@delegance/sdk 0.1.0 → 0.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
@@ -1,6 +1,6 @@
1
1
  // src/types/index.ts
2
2
  import { z } from "zod";
3
- var CompanySizeSchema = z.enum(["1", "2-10", "11-25", "26-50", "51-100", "100-499", "500+"]);
3
+ var CompanySizeSchema = z.enum(["0", "1", "2-10", "11-25", "26-50", "51-100", "100-499", "500+"]);
4
4
  var CreateCompanySchema = z.object({
5
5
  name: z.string().min(1).max(255),
6
6
  legalName: z.string().max(255).optional(),
@@ -22,7 +22,7 @@ var CreateCompanySchema = z.object({
22
22
  zip: z.string().regex(/^\d{5}(-\d{4})?$/)
23
23
  }).optional(),
24
24
  externalId: z.string().max(255).optional(),
25
- metadata: z.record(z.unknown()).optional()
25
+ metadata: z.record(z.string(), z.unknown()).optional()
26
26
  });
27
27
  var DocumentTypeSchema = z.enum([
28
28
  "policy",
@@ -77,13 +77,29 @@ var RateLimitError = class extends DeleganceError {
77
77
  this.name = "RateLimitError";
78
78
  }
79
79
  };
80
+ var ForbiddenError = class extends DeleganceError {
81
+ constructor(message, traceId) {
82
+ super(message, 403, traceId);
83
+ this.name = "ForbiddenError";
84
+ }
85
+ };
86
+ var NotImplementedError = class extends DeleganceError {
87
+ constructor(message, traceId) {
88
+ super(message, 501, traceId);
89
+ this.name = "NotImplementedError";
90
+ }
91
+ };
80
92
 
81
93
  // src/utils/http.ts
82
- var SDK_VERSION = "0.1.0";
94
+ var SDK_VERSION = "0.2.0";
95
+ var DEFAULT_BASE_URL = "https://portal.delegancebrokerage.com";
96
+ function isRetryableStatus(status) {
97
+ return status >= 500 && status !== 501;
98
+ }
83
99
  var HttpClient = class {
84
100
  constructor(config) {
85
101
  this.config = {
86
- baseUrl: (config.baseUrl || "https://app.delegance.ai").replace(/\/$/, ""),
102
+ baseUrl: (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, ""),
87
103
  apiKey: config.apiKey,
88
104
  timeout: config.timeout || 3e4,
89
105
  retries: config.retries ?? 3,
@@ -94,7 +110,8 @@ var HttpClient = class {
94
110
  const { method, path, body, headers = {}, timeout, isFormData } = options;
95
111
  const url = `${this.config.baseUrl}${path}`;
96
112
  const requestHeaders = {
97
- "Authorization": `Bearer ${this.config.apiKey}`,
113
+ // No key = an unauthenticated call (signup, the key exchange).
114
+ ...this.config.apiKey ? { "Authorization": `Bearer ${this.config.apiKey}` } : {},
98
115
  "User-Agent": `delegance-sdk/${SDK_VERSION}`,
99
116
  ...this.config.headers,
100
117
  ...headers
@@ -157,6 +174,7 @@ var HttpClient = class {
157
174
  */
158
175
  async executeWithRetry(buildRequest, timeoutMs) {
159
176
  let lastError = null;
177
+ let lastInformative = null;
160
178
  let attempts = 0;
161
179
  while (attempts <= this.config.retries) {
162
180
  const controller = new AbortController();
@@ -165,21 +183,34 @@ var HttpClient = class {
165
183
  const { url, init } = buildRequest();
166
184
  const response = await fetch(url, { ...init, signal: controller.signal });
167
185
  const data = await response.json().catch(() => ({}));
168
- const traceId = data.traceId;
186
+ const { traceId } = data;
169
187
  if (!response.ok) {
170
- throw this.handleErrorResponse(response.status, data, traceId);
188
+ const err = this.handleErrorResponse(response.status, data, traceId);
189
+ if (typeof data.reason === "string") err.reason = data.reason;
190
+ if (data && typeof data === "object") err.body = data;
191
+ if (err instanceof RateLimitError && err.retryAfter === void 0) {
192
+ const header = Number(response.headers?.get?.("Retry-After"));
193
+ if (Number.isFinite(header) && header > 0) err.retryAfter = header;
194
+ }
195
+ throw err;
171
196
  }
172
197
  return data;
173
198
  } catch (error) {
174
199
  lastError = error;
175
- if (error instanceof AuthenticationError || error instanceof ValidationError || error instanceof NotFoundError || error instanceof RateLimitError) {
200
+ if (error instanceof DeleganceError && error.message && error.message !== "Unknown error") {
201
+ lastInformative = error;
202
+ }
203
+ if (error instanceof DeleganceError && (!isRetryableStatus(error.statusCode) || error.reason === "not_available")) {
176
204
  throw error;
177
205
  }
178
206
  if (attempts < this.config.retries) {
179
207
  attempts++;
180
- await this.delay(Math.pow(2, attempts) * 1e3);
208
+ await this.delay(2 ** attempts * 1e3);
181
209
  continue;
182
210
  }
211
+ if (error instanceof DeleganceError && error.message === "Unknown error" && lastInformative) {
212
+ throw lastInformative;
213
+ }
183
214
  throw error;
184
215
  } finally {
185
216
  clearTimeout(timeoutId);
@@ -189,12 +220,14 @@ var HttpClient = class {
189
220
  }
190
221
  handleErrorResponse(status, data, traceId) {
191
222
  const message = data.message || data.error || "Unknown error";
192
- const details = data.details;
223
+ const { details } = data;
193
224
  switch (status) {
194
225
  case 401:
195
226
  return new AuthenticationError(message, traceId);
196
227
  case 400:
197
228
  return new ValidationError(message, details, traceId);
229
+ case 403:
230
+ return new ForbiddenError(message, traceId);
198
231
  case 404:
199
232
  return new NotFoundError(message, traceId);
200
233
  case 429:
@@ -203,6 +236,8 @@ var HttpClient = class {
203
236
  data.retryAfter,
204
237
  traceId
205
238
  );
239
+ case 501:
240
+ return new NotImplementedError(message, traceId);
206
241
  default:
207
242
  return new DeleganceError(message, status, traceId, details);
208
243
  }
@@ -804,6 +839,7 @@ var QuotesModule = class {
804
839
  };
805
840
 
806
841
  // src/modules/policies.ts
842
+ import { randomUUID } from "crypto";
807
843
  var PoliciesModule = class {
808
844
  constructor(http) {
809
845
  this.http = http;
@@ -868,6 +904,40 @@ var PoliciesModule = class {
868
904
  throw error;
869
905
  }
870
906
  }
907
+ /**
908
+ * Ask to cancel a policy. The request waits for the insured's signature,
909
+ * collected at `signatureUrl` by an owner or admin; a broker reviews the
910
+ * claims-made continuity check before anything reaches the carrier.
911
+ */
912
+ async requestCancellation(policyId, input, opts = {}) {
913
+ return this.http.request({
914
+ method: "POST",
915
+ path: `/api/v1/policies/${encodeURIComponent(policyId)}/cancellation`,
916
+ body: input,
917
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID() }
918
+ });
919
+ }
920
+ /**
921
+ * Sign a cancellation request as an owner or admin. Signing needs a step-up
922
+ * code: call once without `code` to email it (202 with `stepUpId`), then
923
+ * again with `stepUpId` and `code`.
924
+ */
925
+ async signCancellation(requestId, input, opts = {}) {
926
+ return this.http.request({
927
+ method: "POST",
928
+ path: `/api/v1/policy-cancellations/${encodeURIComponent(requestId)}/sign`,
929
+ body: input,
930
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID() }
931
+ });
932
+ }
933
+ /** One cancellation request's status. */
934
+ async getCancellation(requestId) {
935
+ const res = await this.http.request({
936
+ method: "GET",
937
+ path: `/api/v1/policy-cancellations/${encodeURIComponent(requestId)}`
938
+ });
939
+ return res.request;
940
+ }
871
941
  };
872
942
 
873
943
  // src/modules/coi.ts
@@ -1023,6 +1093,7 @@ var SOVModule = class {
1023
1093
  };
1024
1094
 
1025
1095
  // src/modules/api-keys.ts
1096
+ var API_KEY_SCOPES = ["read", "write", "admin", "appetite:read", "mcp:read", "mcp:orin"];
1026
1097
  var ApiKeysModule = class {
1027
1098
  constructor(http) {
1028
1099
  this.http = http;
@@ -1135,8 +1206,18 @@ var ApiKeysModule = class {
1135
1206
  if (idempotency?.idempotencyKey) {
1136
1207
  requestOptions.headers = { "Idempotency-Key": idempotency.idempotencyKey };
1137
1208
  }
1138
- const response = await this.http.request(requestOptions);
1139
- return response.data;
1209
+ try {
1210
+ const response = await this.http.request(requestOptions);
1211
+ return response.data;
1212
+ } catch (error) {
1213
+ if (error instanceof NotFoundError) {
1214
+ throw new NotImplementedError(
1215
+ "Server-side key rotation is not available yet. Create a replacement key with apiKeys.create() using the same scopes, then revoke the old key in the portal.",
1216
+ error.traceId
1217
+ );
1218
+ }
1219
+ throw error;
1220
+ }
1140
1221
  }
1141
1222
  };
1142
1223
 
@@ -1326,6 +1407,519 @@ var OnboardingModule = class {
1326
1407
  });
1327
1408
  return response.data;
1328
1409
  }
1410
+ /** Poll the canonical onboarding state machine. Callable pre-verification. */
1411
+ async state() {
1412
+ const response = await this.http.request({
1413
+ method: "GET",
1414
+ path: "/api/v1/onboarding/state"
1415
+ });
1416
+ return response.data;
1417
+ }
1418
+ /**
1419
+ * Issue (or re-issue) the human verification challenge. Delegance emails a
1420
+ * confirmation link to the company contact email. Hand that link to a human;
1421
+ * the agent cannot self-verify.
1422
+ */
1423
+ async requestVerification() {
1424
+ const response = await this.http.request({
1425
+ method: "POST",
1426
+ path: "/api/v1/onboarding/verification"
1427
+ });
1428
+ return { state: response.data.state, sentTo: response.data.sentTo };
1429
+ }
1430
+ /** Poll state() until `target` is reached or `timeoutMs` elapses. */
1431
+ async waitForState(target, options = {}) {
1432
+ const intervalMs = options.intervalMs ?? 5e3;
1433
+ const timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
1434
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1435
+ throw new ValidationError("intervalMs and timeoutMs must be positive numbers");
1436
+ }
1437
+ const deadline = Date.now() + timeoutMs;
1438
+ for (; ; ) {
1439
+ const current = await this.state();
1440
+ if (current.state === target) return current;
1441
+ if (Date.now() + intervalMs > deadline) {
1442
+ throw new Error(
1443
+ `waitForState('${target}') timed out after ${timeoutMs}ms (last state: ${current.state})`
1444
+ );
1445
+ }
1446
+ await new Promise((r) => setTimeout(r, intervalMs));
1447
+ }
1448
+ }
1449
+ };
1450
+
1451
+ // src/modules/appetite.ts
1452
+ function withoutEnvelope(response) {
1453
+ const copy = { ...response };
1454
+ delete copy.success;
1455
+ return copy;
1456
+ }
1457
+ var AppetiteModule = class {
1458
+ constructor(http) {
1459
+ this.http = http;
1460
+ }
1461
+ /**
1462
+ * Start an appetite match. Returns the result directly on a warm cache hit,
1463
+ * or a queued job to poll with `getMatch()`.
1464
+ *
1465
+ * @example
1466
+ * ```typescript
1467
+ * const start = await client.appetite.match({ state: 'CA', industry: 'restaurant' });
1468
+ * const result = start.status === 'succeeded'
1469
+ * ? start
1470
+ * : await client.appetite.waitForMatch(start.jobId);
1471
+ * ```
1472
+ */
1473
+ async match(input) {
1474
+ if (!input.state || input.state.trim().length !== 2) {
1475
+ throw new ValidationError("state must be a two-letter code");
1476
+ }
1477
+ if (!input.industry && !input.businessDescription && !input.naicsCode && !input.sicCode) {
1478
+ throw new ValidationError("One of industry, businessDescription, naicsCode, or sicCode is required");
1479
+ }
1480
+ const response = await this.http.request({
1481
+ method: "POST",
1482
+ path: "/api/v1/appetite/match",
1483
+ body: input
1484
+ });
1485
+ if (typeof response.jobId === "string" && typeof response.statusUrl === "string") {
1486
+ return {
1487
+ status: "queued",
1488
+ jobId: response.jobId,
1489
+ statusUrl: response.statusUrl,
1490
+ estimatedSeconds: response.estimatedSeconds ?? 20,
1491
+ traceId: response.traceId
1492
+ };
1493
+ }
1494
+ return {
1495
+ ...withoutEnvelope(response),
1496
+ status: "succeeded",
1497
+ cached: true,
1498
+ jobId: null,
1499
+ traceId: response.traceId
1500
+ };
1501
+ }
1502
+ /** Read a match job. Unknown, expired, and other-tenant ids all raise NotFoundError. */
1503
+ async getMatch(jobId) {
1504
+ const response = await this.http.request({
1505
+ method: "GET",
1506
+ path: `/api/v1/appetite/match/${encodeURIComponent(jobId)}`
1507
+ });
1508
+ return withoutEnvelope(response);
1509
+ }
1510
+ /**
1511
+ * Poll a job until it succeeds. Throws on a failed job (with the sanitized
1512
+ * errorCode) or on timeout.
1513
+ */
1514
+ async waitForMatch(jobId, options = {}) {
1515
+ const intervalMs = options.intervalMs ?? 3e3;
1516
+ const timeoutMs = options.timeoutMs ?? 2 * 60 * 1e3;
1517
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1518
+ throw new ValidationError("intervalMs and timeoutMs must be positive numbers");
1519
+ }
1520
+ const deadline = Date.now() + timeoutMs;
1521
+ for (; ; ) {
1522
+ const job = await this.getMatch(jobId);
1523
+ if (job.status === "succeeded") return job;
1524
+ if (job.status === "failed") {
1525
+ throw new DeleganceError(
1526
+ `Appetite match failed: ${job.errorCode}${job.retryable ? " (retryable)" : ""}`,
1527
+ 500,
1528
+ job.traceId
1529
+ );
1530
+ }
1531
+ if (Date.now() + intervalMs > deadline) {
1532
+ throw new DeleganceError(`Appetite match ${jobId} still ${job.status} after ${timeoutMs}ms`, 408);
1533
+ }
1534
+ await new Promise((r) => setTimeout(r, intervalMs));
1535
+ }
1536
+ }
1537
+ };
1538
+
1539
+ // src/modules/leads.ts
1540
+ var LeadsModule = class {
1541
+ constructor(http) {
1542
+ this.http = http;
1543
+ }
1544
+ /**
1545
+ * Create a lead.
1546
+ *
1547
+ * @example
1548
+ * ```typescript
1549
+ * const { companyId } = await client.leads.create({
1550
+ * name: 'Riverbend Bakery',
1551
+ * contactEmail: 'owner@riverbend.example',
1552
+ * state: 'TX',
1553
+ * externalId: 'crm-4411',
1554
+ * });
1555
+ * ```
1556
+ */
1557
+ async create(input) {
1558
+ return this.http.request({
1559
+ method: "POST",
1560
+ path: "/api/v1/leads",
1561
+ body: input
1562
+ });
1563
+ }
1564
+ };
1565
+
1566
+ // src/modules/instant-quotes.ts
1567
+ import { randomUUID as randomUUID2 } from "crypto";
1568
+ var InstantQuotesModule = class {
1569
+ constructor(http) {
1570
+ this.http = http;
1571
+ }
1572
+ /**
1573
+ * `fresh: true` always mints a new submission instead of reusing a warm
1574
+ * session for the same coverages. Use it with `submit({ replace: true })`
1575
+ * to re-quote coverage that already has a request with carriers.
1576
+ */
1577
+ async start(coverages, opts = {}) {
1578
+ const res = await this.http.request({
1579
+ method: "POST",
1580
+ path: "/api/v1/quotes/instant/session",
1581
+ body: opts.fresh ? { coverages, fresh: true } : { coverages }
1582
+ });
1583
+ return res.data;
1584
+ }
1585
+ async submit(input, opts = {}) {
1586
+ const res = await this.http.request({
1587
+ method: "POST",
1588
+ path: "/api/v1/quotes/instant/submit",
1589
+ body: input,
1590
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID2() }
1591
+ });
1592
+ return res.data;
1593
+ }
1594
+ async status(submissionId) {
1595
+ const res = await this.http.request({
1596
+ method: "GET",
1597
+ path: `/api/v1/quotes/instant/${encodeURIComponent(submissionId)}`
1598
+ });
1599
+ return res.data;
1600
+ }
1601
+ /** Polls `status` until every visible carrier resolved, or the timeout. */
1602
+ async waitForQuotes(submissionId, opts = {}) {
1603
+ const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1e3;
1604
+ const intervalMs = opts.intervalMs ?? 1e4;
1605
+ const deadline = Date.now() + timeoutMs;
1606
+ for (; ; ) {
1607
+ const s = await this.status(submissionId);
1608
+ opts.onPoll?.(s);
1609
+ const remaining = deadline - Date.now();
1610
+ if (s.allCarriersResolved || remaining <= 0) return s;
1611
+ await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remaining)));
1612
+ }
1613
+ }
1614
+ /**
1615
+ * After a `no_carriers` submit: the ACORD questions a broker needs to place
1616
+ * the risk with a non-instant market. Pass the answers you submitted so
1617
+ * facts you already gave are not asked again.
1618
+ */
1619
+ async brokerHandoffQuestions(submissionId, instantAnswers = {}) {
1620
+ const res = await this.http.request({
1621
+ method: "POST",
1622
+ path: `/api/v1/quotes/instant/${encodeURIComponent(submissionId)}/broker-handoff/questions`,
1623
+ body: { instantAnswers }
1624
+ });
1625
+ return res.data;
1626
+ }
1627
+ /**
1628
+ * Hands a `no_carriers` submission to a Delegance broker as a non-instant
1629
+ * request, with whatever ACORD answers you have. `complete: false` says you
1630
+ * stopped early; the broker asks the rest. A repeat call returns the same
1631
+ * broker task.
1632
+ */
1633
+ async brokerHandoff(submissionId, input, opts = {}) {
1634
+ const res = await this.http.request({
1635
+ method: "POST",
1636
+ path: `/api/v1/quotes/instant/${encodeURIComponent(submissionId)}/broker-handoff`,
1637
+ body: input,
1638
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID2() }
1639
+ });
1640
+ return res.data;
1641
+ }
1642
+ /** Requests a bind on one offer. A licensed broker reviews it; nothing is charged. */
1643
+ async bind(submissionId, opportunityId, opts = {}) {
1644
+ const res = await this.http.request({
1645
+ method: "POST",
1646
+ path: `/api/v1/quotes/instant/${encodeURIComponent(submissionId)}/bind`,
1647
+ body: { opportunityId },
1648
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID2() }
1649
+ });
1650
+ return res.data;
1651
+ }
1652
+ /**
1653
+ * Bind through fast bind: an owner or admin attests the answers, gets a
1654
+ * 6-digit code by email (the first call returns `stepUpRequired`), then calls
1655
+ * again with `stepUpId` and `code`. A tier A offer returns `checkoutUrl`;
1656
+ * any other offer goes to a broker.
1657
+ */
1658
+ async bindWithStepUp(submissionId, opportunityId, input = {}) {
1659
+ const { idempotencyKey, ...rest } = input;
1660
+ const res = await this.http.request({
1661
+ method: "POST",
1662
+ path: `/api/v1/quotes/instant/${encodeURIComponent(submissionId)}/bind`,
1663
+ body: { opportunityId, ...rest },
1664
+ headers: { "Idempotency-Key": idempotencyKey ?? randomUUID2() }
1665
+ });
1666
+ if (res.stepUpRequired && res.stepUpId) {
1667
+ return { stepUpRequired: true, stepUpId: res.stepUpId, channel: "email", sentTo: res.sentTo ?? "", expiresAt: res.expiresAt ?? null };
1668
+ }
1669
+ return res.data;
1670
+ }
1671
+ };
1672
+
1673
+ // src/modules/payments.ts
1674
+ import { randomUUID as randomUUID3 } from "crypto";
1675
+ var PaymentsModule = class {
1676
+ constructor(http) {
1677
+ this.http = http;
1678
+ }
1679
+ async list() {
1680
+ const res = await this.http.request({ method: "GET", path: "/api/v1/payments" });
1681
+ return res.data.payments;
1682
+ }
1683
+ async checkout(paymentRequestId, opts = {}) {
1684
+ const res = await this.http.request({
1685
+ method: "POST",
1686
+ path: `/api/v1/payments/${encodeURIComponent(paymentRequestId)}/checkout`,
1687
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID3() }
1688
+ });
1689
+ return res.data;
1690
+ }
1691
+ };
1692
+
1693
+ // src/modules/bonds.ts
1694
+ import { randomUUID as randomUUID4 } from "crypto";
1695
+ var BondsModule = class {
1696
+ constructor(http) {
1697
+ this.http = http;
1698
+ }
1699
+ async start(input) {
1700
+ const res = await this.http.request({ method: "POST", path: "/api/v1/bonds/quote", body: input });
1701
+ return res.data;
1702
+ }
1703
+ async answer(sessionId, answers, opts = {}) {
1704
+ const res = await this.http.request({
1705
+ method: "POST",
1706
+ path: `/api/v1/bonds/quote/${encodeURIComponent(sessionId)}/answers`,
1707
+ body: { answers },
1708
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID4() }
1709
+ });
1710
+ return res.data;
1711
+ }
1712
+ async status(sessionId) {
1713
+ const res = await this.http.request({
1714
+ method: "GET",
1715
+ path: `/api/v1/bonds/quote/${encodeURIComponent(sessionId)}`
1716
+ });
1717
+ return res.data;
1718
+ }
1719
+ };
1720
+
1721
+ // src/modules/claims.ts
1722
+ import { randomUUID as randomUUID5 } from "crypto";
1723
+ var ClaimsModule = class {
1724
+ constructor(http) {
1725
+ this.http = http;
1726
+ }
1727
+ async file(input, opts = {}) {
1728
+ const res = await this.http.request({
1729
+ method: "POST",
1730
+ path: "/api/v1/claims",
1731
+ body: input,
1732
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID5() }
1733
+ });
1734
+ return res.data;
1735
+ }
1736
+ async list(opts = {}) {
1737
+ const q = new URLSearchParams();
1738
+ if (opts.status) q.set("status", opts.status);
1739
+ if (opts.limit) q.set("limit", String(opts.limit));
1740
+ const qs = q.toString();
1741
+ const res = await this.http.request({ method: "GET", path: `/api/v1/claims${qs ? `?${qs}` : ""}` });
1742
+ return res.data.claims;
1743
+ }
1744
+ async get(claimId) {
1745
+ const res = await this.http.request({ method: "GET", path: `/api/v1/claims/${encodeURIComponent(claimId)}` });
1746
+ return res.data;
1747
+ }
1748
+ };
1749
+
1750
+ // src/modules/feedback.ts
1751
+ import { randomUUID as randomUUID6 } from "crypto";
1752
+ var FEEDBACK_MESSAGE_MAX = 500;
1753
+ var FeedbackModule = class {
1754
+ constructor(http) {
1755
+ this.http = http;
1756
+ }
1757
+ /**
1758
+ * File a report. Returns the reference id Delegance uses to find it.
1759
+ *
1760
+ * Each call sends one Idempotency-Key, and the client's automatic retries of
1761
+ * that call reuse it, so a lost response never files the report twice. Pass
1762
+ * your own key to make a retry of your own (a second call) safe as well.
1763
+ */
1764
+ async submit(input, opts = {}) {
1765
+ const message = (input.message ?? "").trim();
1766
+ if (!message) throw new Error("Feedback needs a message.");
1767
+ if (message.length > FEEDBACK_MESSAGE_MAX) {
1768
+ throw new Error(`Keep the message to ${FEEDBACK_MESSAGE_MAX} characters or fewer.`);
1769
+ }
1770
+ const body = { type: input.type ?? "bug", message };
1771
+ if (input.context && Object.keys(input.context).length > 0) body.context = input.context;
1772
+ const res = await this.http.request({
1773
+ method: "POST",
1774
+ path: "/api/v1/feedback",
1775
+ body,
1776
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID6() }
1777
+ });
1778
+ return { id: res.data.id };
1779
+ }
1780
+ };
1781
+
1782
+ // src/modules/signup.ts
1783
+ import { createHash, randomBytes, randomUUID as randomUUID7 } from "crypto";
1784
+
1785
+ // src/utils/poll.ts
1786
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1787
+ async function pollUntilReady(poll, opts = {}) {
1788
+ const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
1789
+ const intervalMs = opts.intervalMs ?? 5e3;
1790
+ const deadline = Date.now() + timeoutMs;
1791
+ for (; ; ) {
1792
+ let wait = intervalMs;
1793
+ try {
1794
+ const r = await poll();
1795
+ if (r.status !== "pending") return r;
1796
+ } catch (err) {
1797
+ if (!(err instanceof RateLimitError)) throw err;
1798
+ wait = Math.max(intervalMs, (err.retryAfter ?? 60) * 1e3);
1799
+ }
1800
+ opts.onPoll?.();
1801
+ const remaining = deadline - Date.now();
1802
+ if (remaining <= 0) return { status: "pending" };
1803
+ await sleep(Math.min(wait, remaining));
1804
+ }
1805
+ }
1806
+
1807
+ // src/modules/signup.ts
1808
+ function newDeviceSecret() {
1809
+ const secret = randomBytes(32).toString("hex");
1810
+ return { secret, hash: createHash("sha256").update(secret).digest("hex") };
1811
+ }
1812
+ var SignupModule = class {
1813
+ constructor(makeHttp) {
1814
+ this.makeHttp = makeHttp;
1815
+ }
1816
+ /** Creates the unverified company. Retries safely: sends an Idempotency-Key. */
1817
+ async createAccount(input, opts = {}) {
1818
+ const res = await this.makeHttp("").request({
1819
+ method: "POST",
1820
+ path: "/api/v1/companies",
1821
+ body: input,
1822
+ headers: { "Idempotency-Key": opts.idempotencyKey ?? randomUUID7() }
1823
+ });
1824
+ if (!res.bootstrapKey) {
1825
+ throw new Error("The signup was already created by an earlier attempt; its key is shown only once. Start again with a new signup.");
1826
+ }
1827
+ return {
1828
+ companyId: res.company.id,
1829
+ bootstrapKey: res.bootstrapKey,
1830
+ ...res.warnings?.length ? { warnings: res.warnings } : {}
1831
+ };
1832
+ }
1833
+ async enroll(bootstrapKey, input) {
1834
+ const res = await this.makeHttp(bootstrapKey).request({
1835
+ method: "POST",
1836
+ path: "/api/v1/onboarding/cli-enroll",
1837
+ body: input
1838
+ });
1839
+ return res.data;
1840
+ }
1841
+ async requestVerification(bootstrapKey) {
1842
+ const res = await this.makeHttp(bootstrapKey).request({
1843
+ method: "POST",
1844
+ path: "/api/v1/onboarding/verification",
1845
+ body: {}
1846
+ });
1847
+ return { sentTo: res.data?.sentTo };
1848
+ }
1849
+ /**
1850
+ * One poll. `pending` until the email link is clicked. Throws with
1851
+ * `reason` = already_collected | account_exists (409) or expired (410).
1852
+ */
1853
+ async exchange(enrollmentId, deviceSecret) {
1854
+ const res = await this.makeHttp("").request({
1855
+ method: "POST",
1856
+ path: "/api/v1/onboarding/cli-exchange",
1857
+ body: { enrollmentId, deviceSecret }
1858
+ });
1859
+ if (res.data?.apiKey && res.data.keyPrefix && res.data.companyId) {
1860
+ return { status: "ready", apiKey: res.data.apiKey, keyPrefix: res.data.keyPrefix, companyId: res.data.companyId };
1861
+ }
1862
+ if (res.data?.status === "pending") return { status: "pending" };
1863
+ throw new Error('Unexpected response from the key exchange. Run "delegance verify" to retry.');
1864
+ }
1865
+ /**
1866
+ * Polls `exchange` until the key is ready or `timeoutMs` passes. A 429 waits
1867
+ * the server's Retry-After and keeps polling.
1868
+ */
1869
+ async waitForKey(enrollmentId, deviceSecret, opts = {}) {
1870
+ return pollUntilReady(() => this.exchange(enrollmentId, deviceSecret), opts);
1871
+ }
1872
+ };
1873
+
1874
+ // src/modules/auth.ts
1875
+ var DeviceLoginModule = class {
1876
+ constructor(makeHttp) {
1877
+ this.makeHttp = makeHttp;
1878
+ }
1879
+ async start(input) {
1880
+ const res = await this.makeHttp("").request({
1881
+ method: "POST",
1882
+ path: "/api/v1/auth/device",
1883
+ body: input
1884
+ });
1885
+ return res.data;
1886
+ }
1887
+ /**
1888
+ * One poll. `pending` until the portal user approves. Throws with `reason`
1889
+ * = access_denied (403), already_collected (409), or expired (410).
1890
+ */
1891
+ async poll(loginId, deviceSecret) {
1892
+ const res = await this.makeHttp("").request({
1893
+ method: "POST",
1894
+ path: "/api/v1/auth/device/token",
1895
+ body: { loginId, deviceSecret }
1896
+ });
1897
+ const d = res.data ?? {};
1898
+ if (d.apiKey && d.keyPrefix && d.companyId) {
1899
+ return { status: "ready", apiKey: d.apiKey, keyPrefix: d.keyPrefix, companyId: d.companyId, scopes: d.scopes ?? [], expiresAt: d.expiresAt ?? null };
1900
+ }
1901
+ if (d.status === "pending") return { status: "pending" };
1902
+ throw new Error('Unexpected response from the login poll. Run "delegance auth login" again.');
1903
+ }
1904
+ /** Polls until the key is ready or `timeoutMs` passes (then returns `pending`). */
1905
+ async waitForKey(loginId, deviceSecret, opts = {}) {
1906
+ return pollUntilReady(() => this.poll(loginId, deviceSecret), opts);
1907
+ }
1908
+ };
1909
+ var AuthModule = class {
1910
+ constructor(http) {
1911
+ this.http = http;
1912
+ }
1913
+ /** The calling key: prefix, origin, scopes, company, and expiry. */
1914
+ async keyInfo() {
1915
+ const res = await this.http.request({ method: "GET", path: "/api/v1/auth/key" });
1916
+ return res.data;
1917
+ }
1918
+ /** Extend a CLI key to 90 days from now. Throws `not_refreshable` (400) for portal keys. */
1919
+ async refresh() {
1920
+ const res = await this.http.request({ method: "POST", path: "/api/v1/auth/refresh", body: {} });
1921
+ return res.data;
1922
+ }
1329
1923
  };
1330
1924
 
1331
1925
  // src/client.ts
@@ -1358,6 +1952,14 @@ var DeleganceClient = class {
1358
1952
  this.usage = new UsageModule(this.http);
1359
1953
  this.knowledge = new KnowledgeModule(this.http);
1360
1954
  this.onboarding = new OnboardingModule(this.http);
1955
+ this.appetite = new AppetiteModule(this.http);
1956
+ this.leads = new LeadsModule(this.http);
1957
+ this.instantQuotes = new InstantQuotesModule(this.http);
1958
+ this.payments = new PaymentsModule(this.http);
1959
+ this.bonds = new BondsModule(this.http);
1960
+ this.claims = new ClaimsModule(this.http);
1961
+ this.feedback = new FeedbackModule(this.http);
1962
+ this.auth = new AuthModule(this.http);
1361
1963
  }
1362
1964
  /**
1363
1965
  * Get the SDK version
@@ -1398,28 +2000,52 @@ function createClientFromEnv() {
1398
2000
  baseUrl
1399
2001
  });
1400
2002
  }
2003
+ function createSignup(config = {}) {
2004
+ return new SignupModule((apiKey) => new HttpClient({ apiKey, ...config }));
2005
+ }
2006
+ function createDeviceLogin(config = {}) {
2007
+ return new DeviceLoginModule((apiKey) => new HttpClient({ apiKey, ...config }));
2008
+ }
1401
2009
  export {
2010
+ API_KEY_SCOPES,
1402
2011
  ApiKeysModule,
2012
+ AppetiteModule,
2013
+ AuthModule,
1403
2014
  AuthenticationError,
2015
+ BondsModule,
1404
2016
  COIModule,
1405
2017
  ChatModule,
2018
+ ClaimsModule,
1406
2019
  CompaniesModule,
1407
2020
  CompanySizeSchema,
1408
2021
  CreateCompanySchema,
2022
+ DEFAULT_BASE_URL,
1409
2023
  DeleganceClient,
1410
2024
  DeleganceError,
2025
+ DeviceLoginModule,
1411
2026
  DocumentStatusSchema,
1412
2027
  DocumentTypeSchema,
1413
2028
  DocumentsModule,
2029
+ FEEDBACK_MESSAGE_MAX,
2030
+ FeedbackModule,
2031
+ ForbiddenError,
2032
+ InstantQuotesModule,
1414
2033
  KnowledgeModule,
2034
+ LeadsModule,
1415
2035
  NotFoundError,
2036
+ NotImplementedError,
1416
2037
  OnboardingModule,
2038
+ PaymentsModule,
1417
2039
  PoliciesModule,
1418
2040
  QuotesModule,
1419
2041
  RateLimitError,
1420
2042
  SDK_VERSION,
1421
2043
  SOVModule,
2044
+ SignupModule,
1422
2045
  UsageModule,
1423
2046
  ValidationError,
1424
- createClientFromEnv
2047
+ createClientFromEnv,
2048
+ createDeviceLogin,
2049
+ createSignup,
2050
+ newDeviceSecret
1425
2051
  };