@signaliz/sdk 1.0.60 → 1.0.61

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
@@ -73,6 +73,12 @@ const signalDiscovery = await signaliz.discoverSignals({
73
73
  limit: 100,
74
74
  sources: ['web', 'news'],
75
75
  });
76
+ const painSignalDiscovery = await signaliz.discoverSignals({
77
+ query: 'companies showing signals they need account-signal automation',
78
+ icpContext: 'B2B SaaS companies with outbound sales teams and manual account research',
79
+ limit: 100,
80
+ sources: ['web', 'news'],
81
+ });
76
82
  const completedDiscovery = signalDiscovery.signalSearchRunId
77
83
  ? await signaliz.discoverSignals({ signalSearchRunId: signalDiscovery.signalSearchRunId })
78
84
  : signalDiscovery;
@@ -154,12 +160,15 @@ Signal to Copy also shares company research across copy recipients.
154
160
  Signals Everything is query-first rather than a row batch. It defaults to 100
155
161
  distinct companies and supports up to 1,000. Every returned signal contains a
156
162
  company name, company domain, dated evidence, and a source URL. The typed
157
- `costGuardrail` reports both projected and observed source acquisition cost;
163
+ `costGuardrail` reports projected and observed source acquisition cost plus
164
+ identity-resolution cost, receipt source, and fallback-provider request count;
158
165
  Signaliz returns signals only when cost stays strictly below $0.01 per qualified
159
166
  company. A request is a ceiling, not a promise: it can return fewer results
160
167
  when public evidence cannot verify both a date and company identity. It is
161
168
  included on Team and Agency plans and can be resumed with
162
- `signalSearchRunId` without duplicate source acquisition.
169
+ `signalSearchRunId` without duplicate source acquisition. Pass `icpContext` to
170
+ ground an open-ended request such as “companies showing signals they need our
171
+ product” in the user's target market.
163
172
 
164
173
  Company Signal Enrichment defaults `online` and `enableDeepSearch` to `true`
165
174
  unless the caller explicitly disables them. Its `searchMode` defaults to
@@ -170,6 +179,13 @@ complete, evidence-backed email variations when supported signals are available.
170
179
  Signal to Copy is cache-first and defaults `enableDeepSearch` to `false` so it
171
180
  normally finishes within an agent turn; set `skipCache` or `enableDeepSearch`
172
181
  only when fresh or deeper research is required.
182
+ Every core request uses the authenticated workspace. Find Email and Verify Email
183
+ honor that workspace's configured cache window; eligible hits cost 0 credits,
184
+ while fresh discovery or verification is charged on every plan. Team, Agency,
185
+ and Pay-As-You-Go include fresh Company Signals and Signal to Copy. Those plans
186
+ still check eligible workspace signal data first even when Company Signals is
187
+ set to No cache. `lookbackDays` remains a hard evidence-age bound for both
188
+ cached and fresh Company Signals and Signal to Copy results.
173
189
  Unclassified evidence is returned by signal enrichment but is not used to
174
190
  generate outreach copy.
175
191
  Each normalized signal keeps `date` separate from crawl-time `detectedAt` and
@@ -9,17 +9,21 @@ var SignalizError = class extends Error {
9
9
  this.details = data.details;
10
10
  }
11
11
  get isRetryable() {
12
+ if (typeof this.details?.retry_eligible === "boolean") {
13
+ return this.details.retry_eligible;
14
+ }
12
15
  return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
13
16
  }
14
17
  };
15
18
  function parseError(status, body) {
16
19
  if (body?.error && typeof body.error === "object") {
20
+ const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
17
21
  return new SignalizError({
18
22
  code: body.error.code || `HTTP_${status}`,
19
23
  message: body.error.message || `Request failed with status ${status}`,
20
24
  errorType: mapErrorType(body.error.code, status),
21
25
  retryAfter: retryAfterSeconds(body.error),
22
- details: body.error.details
26
+ details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
23
27
  });
24
28
  }
25
29
  const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
@@ -28,7 +32,7 @@ function parseError(status, body) {
28
32
  message: body?.message || body?.error || `Request failed with status ${status}`,
29
33
  errorType: mapErrorType(code, status),
30
34
  retryAfter: retryAfterSeconds(body),
31
- details: body?.details || publicRetryDetails(body)
35
+ details: mergePublicErrorDetails(body, body?.details)
32
36
  });
33
37
  }
34
38
  function retryAfterSeconds(body) {
@@ -40,13 +44,26 @@ function publicRetryDetails(body) {
40
44
  ...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
41
45
  ...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
42
46
  ...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
47
+ ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
48
+ ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
49
+ ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
50
+ ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
51
+ ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
43
52
  ...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
53
+ ...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
44
54
  ...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
45
55
  ...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
46
56
  ...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
47
57
  };
48
58
  return Object.keys(details).length > 0 ? details : void 0;
49
59
  }
60
+ function mergePublicErrorDetails(body, explicitDetails) {
61
+ const merged = { ...publicRetryDetails(body) || {} };
62
+ if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
63
+ Object.assign(merged, explicitDetails);
64
+ }
65
+ return Object.keys(merged).length > 0 ? merged : void 0;
66
+ }
50
67
  function mapErrorType(code, status) {
51
68
  if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
52
69
  if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
@@ -108,7 +125,9 @@ var HttpClient = class {
108
125
  const errBody = await res.json().catch(() => ({}));
109
126
  const err = parseError(res.status, errBody);
110
127
  if (err.isRetryable && attempt < this.maxRetries) {
111
- const wait = err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
128
+ const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
129
+ if (retryWithNewKey) throw err;
130
+ const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
112
131
  await sleep(wait);
113
132
  lastError = err;
114
133
  continue;
@@ -765,7 +784,10 @@ var Signaliz = class {
765
784
  );
766
785
  const uniqueTasks = deduplicated.uniqueItems;
767
786
  const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
768
- if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
787
+ const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
788
+ (task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
789
+ );
790
+ if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
769
791
  const rows = await this.runRecoverableCoreBatchJob(
770
792
  path,
771
793
  uniqueTasks.map((task) => task.request),
@@ -891,13 +913,27 @@ function batchItemFailure(index, error, raw) {
891
913
  raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
892
914
  );
893
915
  const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
916
+ const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
917
+ const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
918
+ const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
919
+ const runId = raw?.run_id ?? raw?.runId;
920
+ const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
921
+ const jobId = raw?.job_id ?? raw?.jobId;
922
+ const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
894
923
  return {
895
924
  index,
896
925
  success: false,
897
926
  error,
898
927
  ...errorCode ? { errorCode } : {},
899
928
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
900
- ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
929
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
930
+ ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
931
+ ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
932
+ ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
933
+ ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
934
+ ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
935
+ ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
936
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
901
937
  };
902
938
  }
903
939
  function recoverableJobRowFailed(row) {
@@ -912,6 +948,7 @@ function compact(input) {
912
948
  }
913
949
  function findEmailRequestBody(params) {
914
950
  return compact({
951
+ run_id: params.runId,
915
952
  company_domain: params.companyDomain,
916
953
  full_name: params.fullName,
917
954
  first_name: params.firstName,
@@ -951,6 +988,7 @@ function normalizeCoreProductDryRunResult(data) {
951
988
  };
952
989
  }
953
990
  function normalizeFindEmailResult(data) {
991
+ const processing = String(data.status || "").toLowerCase() === "processing";
954
992
  const failed = coreProductResponseFailed(data);
955
993
  const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
956
994
  const email = failed ? null : rawEmail;
@@ -963,7 +1001,7 @@ function normalizeFindEmailResult(data) {
963
1001
  const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
964
1002
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
965
1003
  return {
966
- success: !failed && found,
1004
+ success: processing || !failed && found,
967
1005
  found,
968
1006
  email,
969
1007
  firstName: data.first_name,
@@ -990,8 +1028,12 @@ function normalizeFindEmailResult(data) {
990
1028
  liveProviderCalled: data.live_provider_called,
991
1029
  openrouterCalled: data.openrouter_called,
992
1030
  byoOpenrouterUsed: data.byo_openrouter_used,
993
- error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
994
- errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1031
+ error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1032
+ errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1033
+ status: processing ? "processing" : "completed",
1034
+ runId: data.run_id,
1035
+ nextPollAfterSeconds: data.next_poll_after_seconds,
1036
+ suggestedAction: data.suggested_action,
995
1037
  raw: data
996
1038
  };
997
1039
  }
@@ -1127,6 +1169,12 @@ function validateSignalDiscoveryParams(params) {
1127
1169
  if (query && (query.length < 2 || query.length > 2e3)) {
1128
1170
  throw new RangeError("query must contain between 2 and 2000 characters");
1129
1171
  }
1172
+ if (params.icpContext !== void 0) {
1173
+ const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
1174
+ if (icpContext.length < 2 || icpContext.length > 4e3) {
1175
+ throw new RangeError("icpContext must contain between 2 and 4000 characters");
1176
+ }
1177
+ }
1130
1178
  if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
1131
1179
  throw new RangeError("limit must be an integer between 1 and 1000");
1132
1180
  }
@@ -1151,6 +1199,7 @@ function signalDiscoveryRequestBody(params) {
1151
1199
  }
1152
1200
  return compact({
1153
1201
  query: params.query?.trim(),
1202
+ icp_context: params.icpContext?.trim(),
1154
1203
  limit: params.limit ?? 100,
1155
1204
  sources: params.sources,
1156
1205
  lookback_days: params.lookbackDays,
@@ -1176,6 +1225,8 @@ function normalizeSignalDiscoveryResult(params, data) {
1176
1225
  availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
1177
1226
  sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
1178
1227
  evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
1228
+ discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
1229
+ identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
1179
1230
  costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
1180
1231
  warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
1181
1232
  raw: data
@@ -1204,7 +1255,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
1204
1255
  guardrail.projected_cost_per_requested_company_signal_usd
1205
1256
  ),
1206
1257
  sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
1207
- costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1258
+ costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1259
+ identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
1260
+ identityCostSource: [
1261
+ "observed_zero_credit_receipts",
1262
+ "contractual_zero_cost",
1263
+ "not_used",
1264
+ "unavailable"
1265
+ ].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
1266
+ identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
1208
1267
  qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
1209
1268
  returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
1210
1269
  availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
@@ -1308,6 +1367,7 @@ function normalizedFindEmailBatchName(request) {
1308
1367
  function coreProductBatchRequestKey(path, request) {
1309
1368
  if (path === "api/v1/find-email") {
1310
1369
  return JSON.stringify([
1370
+ normalizedBatchText(request.run_id),
1311
1371
  normalizedFindEmailBatchName(request),
1312
1372
  normalizedBatchDomain(request.company_domain),
1313
1373
  normalizedBatchLinkedIn(request.linkedin_url),
@@ -1413,6 +1473,7 @@ function signalToCopyRequestBody(params) {
1413
1473
  title: params.title,
1414
1474
  campaign_offer: params.campaignOffer,
1415
1475
  research_prompt: params.researchPrompt,
1476
+ lookback_days: params.lookbackDays,
1416
1477
  signal_run_id: params.signalRunId,
1417
1478
  skip_cache: params.skipCache,
1418
1479
  enable_deep_search: params.enableDeepSearch,
@@ -1423,6 +1484,9 @@ function signalToCopyRequestBody(params) {
1423
1484
  }
1424
1485
  function validateSignalToCopyParams(params) {
1425
1486
  validateCoreProductMaxWaitMs(params.maxWaitMs);
1487
+ if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
1488
+ throw new RangeError("lookbackDays must be an integer between 1 and 365");
1489
+ }
1426
1490
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1427
1491
  const missing = [
1428
1492
  ["companyDomain", params.companyDomain],
package/dist/index.d.mts CHANGED
@@ -27,6 +27,20 @@ interface BatchItemResult<T> {
27
27
  retryEligible?: boolean;
28
28
  /** Server-provided delay before retrying the failed row. */
29
29
  retryAfterSeconds?: number;
30
+ /** Explicit recovery strategy. A fresh key can repeat provider work and is never automatic. */
31
+ retryStrategy?: 'new_idempotency_key';
32
+ /** Present only when the server proves the failed row consumed zero Signaliz credits. */
33
+ creditsUsed?: 0;
34
+ /** Present only when the server proves the failed row charged zero Signaliz credits. */
35
+ creditsCharged?: 0;
36
+ /** Existing provider run that can be resumed without dispatching new work. */
37
+ runId?: string;
38
+ /** Existing Verify Email run that can be resumed without dispatching new work. */
39
+ verificationRunId?: string;
40
+ /** Existing recoverable batch job. */
41
+ jobId?: string;
42
+ /** Machine-readable recovery action returned by Signaliz. */
43
+ suggestedAction?: string;
30
44
  /** Zero-based input index containing the full result for this exact duplicate. */
31
45
  duplicateOf?: number;
32
46
  }
@@ -66,8 +80,7 @@ interface CoreProductDryRunResult {
66
80
  sideEffects: unknown[];
67
81
  raw: Record<string, unknown>;
68
82
  }
69
- interface FindEmailParams {
70
- companyDomain: string;
83
+ interface FindEmailCommonParams {
71
84
  fullName?: string;
72
85
  firstName?: string;
73
86
  lastName?: string;
@@ -80,6 +93,15 @@ interface FindEmailParams {
80
93
  /** Stable retry key for recovering the same request after an ambiguous response. */
81
94
  idempotencyKey?: string;
82
95
  }
96
+ type FindEmailParams = FindEmailCommonParams & ({
97
+ companyDomain: string;
98
+ /** Omit for a new lookup. */
99
+ runId?: undefined;
100
+ } | {
101
+ /** Resume one existing Find Email run without dispatching a new finder. */
102
+ runId: string;
103
+ companyDomain?: string;
104
+ });
83
105
  interface FindEmailResult {
84
106
  success: boolean;
85
107
  found: boolean;
@@ -112,6 +134,12 @@ interface FindEmailResult {
112
134
  byoOpenrouterUsed?: boolean;
113
135
  error?: string;
114
136
  errorCode?: string;
137
+ /** Processing while an existing provider run is still active; otherwise completed. */
138
+ status?: 'processing' | 'completed';
139
+ /** Durable Find Email run ID for polling without duplicate spend. */
140
+ runId?: string;
141
+ nextPollAfterSeconds?: number;
142
+ suggestedAction?: string;
115
143
  raw: Record<string, unknown>;
116
144
  }
117
145
  interface VerifyEmailResult {
@@ -250,8 +278,10 @@ interface CompanySignalEnrichmentResult {
250
278
  type SignalDiscoverySource = 'web' | 'news';
251
279
  type SignalDiscoveryStatus = 'planned' | 'queued' | 'processing' | 'completed' | 'completed_with_warnings' | 'failed';
252
280
  interface SignalDiscoveryParams {
253
- /** Natural-language description of the signals to find. Required for a new search. */
281
+ /** Any natural-language signal question. Required for a new search. */
254
282
  query?: string;
283
+ /** Optional natural-language ICP, product, or pain context that returned signals must support. */
284
+ icpContext?: string;
255
285
  /** Number of qualified company signals requested. Defaults to 100; allowed range is 1-1,000. */
256
286
  limit?: number;
257
287
  /** Restrict discovery to selected source families. */
@@ -300,7 +330,13 @@ interface SignalDiscoveryCostGuardrail {
300
330
  maxSourceCostUsd?: number | null;
301
331
  projectedCostPerRequestedCompanySignalUsd?: number | null;
302
332
  sourceCostUsd?: number | null;
303
- costSource?: 'observed' | 'catalog_bounded' | 'mixed' | 'unavailable';
333
+ costSource?: 'observed' | 'catalog_preflight' | 'catalog_bounded' | 'mixed' | 'unavailable';
334
+ /** Separately reported company identity-resolution cost; null when receipts are incomplete. */
335
+ identityResolutionCostUsd?: number | null;
336
+ /** Evidence used to account for company identity-resolution cost. */
337
+ identityCostSource?: 'observed_zero_credit_receipts' | 'contractual_zero_cost' | 'not_used' | 'unavailable';
338
+ /** Zero-cost fallback identity-provider requests covered by identityResolutionCostUsd. */
339
+ identityProviderRequests?: number;
304
340
  qualifiedCompanySignalCount?: number;
305
341
  returnedCompanySignalCount?: number;
306
342
  /** Qualified signals retained on the same search run for later expansion. */
@@ -326,6 +362,10 @@ interface SignalDiscoveryResult {
326
362
  availableCompanySignalCount?: number;
327
363
  sourceLanes?: string[];
328
364
  evidenceValidation?: Record<string, unknown>;
365
+ /** Search, reachability, date, classifier, and rejection counts for diagnosis. */
366
+ discoveryDiagnostics?: Record<string, unknown>;
367
+ /** Company-domain verification counts for the retained cohort. */
368
+ identityResolution?: Record<string, unknown>;
329
369
  costGuardrail?: SignalDiscoveryCostGuardrail;
330
370
  warnings?: string[];
331
371
  raw: Record<string, unknown>;
@@ -336,6 +376,8 @@ interface SignalToCopyParams {
336
376
  title?: string;
337
377
  campaignOffer?: string;
338
378
  researchPrompt?: string;
379
+ /** Maximum signal age used for cache reuse and fresh research. Defaults to 90 days. */
380
+ lookbackDays?: number;
339
381
  signalRunId?: string;
340
382
  /** Bypass cached company signals and start fresh research. */
341
383
  skipCache?: boolean;
package/dist/index.d.ts CHANGED
@@ -27,6 +27,20 @@ interface BatchItemResult<T> {
27
27
  retryEligible?: boolean;
28
28
  /** Server-provided delay before retrying the failed row. */
29
29
  retryAfterSeconds?: number;
30
+ /** Explicit recovery strategy. A fresh key can repeat provider work and is never automatic. */
31
+ retryStrategy?: 'new_idempotency_key';
32
+ /** Present only when the server proves the failed row consumed zero Signaliz credits. */
33
+ creditsUsed?: 0;
34
+ /** Present only when the server proves the failed row charged zero Signaliz credits. */
35
+ creditsCharged?: 0;
36
+ /** Existing provider run that can be resumed without dispatching new work. */
37
+ runId?: string;
38
+ /** Existing Verify Email run that can be resumed without dispatching new work. */
39
+ verificationRunId?: string;
40
+ /** Existing recoverable batch job. */
41
+ jobId?: string;
42
+ /** Machine-readable recovery action returned by Signaliz. */
43
+ suggestedAction?: string;
30
44
  /** Zero-based input index containing the full result for this exact duplicate. */
31
45
  duplicateOf?: number;
32
46
  }
@@ -66,8 +80,7 @@ interface CoreProductDryRunResult {
66
80
  sideEffects: unknown[];
67
81
  raw: Record<string, unknown>;
68
82
  }
69
- interface FindEmailParams {
70
- companyDomain: string;
83
+ interface FindEmailCommonParams {
71
84
  fullName?: string;
72
85
  firstName?: string;
73
86
  lastName?: string;
@@ -80,6 +93,15 @@ interface FindEmailParams {
80
93
  /** Stable retry key for recovering the same request after an ambiguous response. */
81
94
  idempotencyKey?: string;
82
95
  }
96
+ type FindEmailParams = FindEmailCommonParams & ({
97
+ companyDomain: string;
98
+ /** Omit for a new lookup. */
99
+ runId?: undefined;
100
+ } | {
101
+ /** Resume one existing Find Email run without dispatching a new finder. */
102
+ runId: string;
103
+ companyDomain?: string;
104
+ });
83
105
  interface FindEmailResult {
84
106
  success: boolean;
85
107
  found: boolean;
@@ -112,6 +134,12 @@ interface FindEmailResult {
112
134
  byoOpenrouterUsed?: boolean;
113
135
  error?: string;
114
136
  errorCode?: string;
137
+ /** Processing while an existing provider run is still active; otherwise completed. */
138
+ status?: 'processing' | 'completed';
139
+ /** Durable Find Email run ID for polling without duplicate spend. */
140
+ runId?: string;
141
+ nextPollAfterSeconds?: number;
142
+ suggestedAction?: string;
115
143
  raw: Record<string, unknown>;
116
144
  }
117
145
  interface VerifyEmailResult {
@@ -250,8 +278,10 @@ interface CompanySignalEnrichmentResult {
250
278
  type SignalDiscoverySource = 'web' | 'news';
251
279
  type SignalDiscoveryStatus = 'planned' | 'queued' | 'processing' | 'completed' | 'completed_with_warnings' | 'failed';
252
280
  interface SignalDiscoveryParams {
253
- /** Natural-language description of the signals to find. Required for a new search. */
281
+ /** Any natural-language signal question. Required for a new search. */
254
282
  query?: string;
283
+ /** Optional natural-language ICP, product, or pain context that returned signals must support. */
284
+ icpContext?: string;
255
285
  /** Number of qualified company signals requested. Defaults to 100; allowed range is 1-1,000. */
256
286
  limit?: number;
257
287
  /** Restrict discovery to selected source families. */
@@ -300,7 +330,13 @@ interface SignalDiscoveryCostGuardrail {
300
330
  maxSourceCostUsd?: number | null;
301
331
  projectedCostPerRequestedCompanySignalUsd?: number | null;
302
332
  sourceCostUsd?: number | null;
303
- costSource?: 'observed' | 'catalog_bounded' | 'mixed' | 'unavailable';
333
+ costSource?: 'observed' | 'catalog_preflight' | 'catalog_bounded' | 'mixed' | 'unavailable';
334
+ /** Separately reported company identity-resolution cost; null when receipts are incomplete. */
335
+ identityResolutionCostUsd?: number | null;
336
+ /** Evidence used to account for company identity-resolution cost. */
337
+ identityCostSource?: 'observed_zero_credit_receipts' | 'contractual_zero_cost' | 'not_used' | 'unavailable';
338
+ /** Zero-cost fallback identity-provider requests covered by identityResolutionCostUsd. */
339
+ identityProviderRequests?: number;
304
340
  qualifiedCompanySignalCount?: number;
305
341
  returnedCompanySignalCount?: number;
306
342
  /** Qualified signals retained on the same search run for later expansion. */
@@ -326,6 +362,10 @@ interface SignalDiscoveryResult {
326
362
  availableCompanySignalCount?: number;
327
363
  sourceLanes?: string[];
328
364
  evidenceValidation?: Record<string, unknown>;
365
+ /** Search, reachability, date, classifier, and rejection counts for diagnosis. */
366
+ discoveryDiagnostics?: Record<string, unknown>;
367
+ /** Company-domain verification counts for the retained cohort. */
368
+ identityResolution?: Record<string, unknown>;
329
369
  costGuardrail?: SignalDiscoveryCostGuardrail;
330
370
  warnings?: string[];
331
371
  raw: Record<string, unknown>;
@@ -336,6 +376,8 @@ interface SignalToCopyParams {
336
376
  title?: string;
337
377
  campaignOffer?: string;
338
378
  researchPrompt?: string;
379
+ /** Maximum signal age used for cache reuse and fresh research. Defaults to 90 days. */
380
+ lookbackDays?: number;
339
381
  signalRunId?: string;
340
382
  /** Bypass cached company signals and start fresh research. */
341
383
  skipCache?: boolean;
package/dist/index.js CHANGED
@@ -36,17 +36,21 @@ var SignalizError = class extends Error {
36
36
  this.details = data.details;
37
37
  }
38
38
  get isRetryable() {
39
+ if (typeof this.details?.retry_eligible === "boolean") {
40
+ return this.details.retry_eligible;
41
+ }
39
42
  return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
40
43
  }
41
44
  };
42
45
  function parseError(status, body) {
43
46
  if (body?.error && typeof body.error === "object") {
47
+ const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
44
48
  return new SignalizError({
45
49
  code: body.error.code || `HTTP_${status}`,
46
50
  message: body.error.message || `Request failed with status ${status}`,
47
51
  errorType: mapErrorType(body.error.code, status),
48
52
  retryAfter: retryAfterSeconds(body.error),
49
- details: body.error.details
53
+ details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
50
54
  });
51
55
  }
52
56
  const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
@@ -55,7 +59,7 @@ function parseError(status, body) {
55
59
  message: body?.message || body?.error || `Request failed with status ${status}`,
56
60
  errorType: mapErrorType(code, status),
57
61
  retryAfter: retryAfterSeconds(body),
58
- details: body?.details || publicRetryDetails(body)
62
+ details: mergePublicErrorDetails(body, body?.details)
59
63
  });
60
64
  }
61
65
  function retryAfterSeconds(body) {
@@ -67,13 +71,26 @@ function publicRetryDetails(body) {
67
71
  ...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
68
72
  ...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
69
73
  ...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
74
+ ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
75
+ ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
76
+ ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
77
+ ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
78
+ ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
70
79
  ...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
80
+ ...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
71
81
  ...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
72
82
  ...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
73
83
  ...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
74
84
  };
75
85
  return Object.keys(details).length > 0 ? details : void 0;
76
86
  }
87
+ function mergePublicErrorDetails(body, explicitDetails) {
88
+ const merged = { ...publicRetryDetails(body) || {} };
89
+ if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
90
+ Object.assign(merged, explicitDetails);
91
+ }
92
+ return Object.keys(merged).length > 0 ? merged : void 0;
93
+ }
77
94
  function mapErrorType(code, status) {
78
95
  if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
79
96
  if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
@@ -135,7 +152,9 @@ var HttpClient = class {
135
152
  const errBody = await res.json().catch(() => ({}));
136
153
  const err = parseError(res.status, errBody);
137
154
  if (err.isRetryable && attempt < this.maxRetries) {
138
- const wait = err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
155
+ const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
156
+ if (retryWithNewKey) throw err;
157
+ const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
139
158
  await sleep(wait);
140
159
  lastError = err;
141
160
  continue;
@@ -792,7 +811,10 @@ var Signaliz = class {
792
811
  );
793
812
  const uniqueTasks = deduplicated.uniqueItems;
794
813
  const recoverableBatch = path === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
795
- if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
814
+ const containsFindRecoveryReads = path === "api/v1/find-email" && uniqueTasks.some(
815
+ (task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
816
+ );
817
+ if (path !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
796
818
  const rows = await this.runRecoverableCoreBatchJob(
797
819
  path,
798
820
  uniqueTasks.map((task) => task.request),
@@ -918,13 +940,27 @@ function batchItemFailure(index, error, raw) {
918
940
  raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
919
941
  );
920
942
  const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
943
+ const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
944
+ const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
945
+ const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
946
+ const runId = raw?.run_id ?? raw?.runId;
947
+ const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
948
+ const jobId = raw?.job_id ?? raw?.jobId;
949
+ const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
921
950
  return {
922
951
  index,
923
952
  success: false,
924
953
  error,
925
954
  ...errorCode ? { errorCode } : {},
926
955
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
927
- ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
956
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
957
+ ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
958
+ ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
959
+ ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
960
+ ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
961
+ ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
962
+ ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
963
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
928
964
  };
929
965
  }
930
966
  function recoverableJobRowFailed(row) {
@@ -939,6 +975,7 @@ function compact(input) {
939
975
  }
940
976
  function findEmailRequestBody(params) {
941
977
  return compact({
978
+ run_id: params.runId,
942
979
  company_domain: params.companyDomain,
943
980
  full_name: params.fullName,
944
981
  first_name: params.firstName,
@@ -978,6 +1015,7 @@ function normalizeCoreProductDryRunResult(data) {
978
1015
  };
979
1016
  }
980
1017
  function normalizeFindEmailResult(data) {
1018
+ const processing = String(data.status || "").toLowerCase() === "processing";
981
1019
  const failed = coreProductResponseFailed(data);
982
1020
  const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
983
1021
  const email = failed ? null : rawEmail;
@@ -990,7 +1028,7 @@ function normalizeFindEmailResult(data) {
990
1028
  const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
991
1029
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
992
1030
  return {
993
- success: !failed && found,
1031
+ success: processing || !failed && found,
994
1032
  found,
995
1033
  email,
996
1034
  firstName: data.first_name,
@@ -1017,8 +1055,12 @@ function normalizeFindEmailResult(data) {
1017
1055
  liveProviderCalled: data.live_provider_called,
1018
1056
  openrouterCalled: data.openrouter_called,
1019
1057
  byoOpenrouterUsed: data.byo_openrouter_used,
1020
- error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1021
- errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1058
+ error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1059
+ errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1060
+ status: processing ? "processing" : "completed",
1061
+ runId: data.run_id,
1062
+ nextPollAfterSeconds: data.next_poll_after_seconds,
1063
+ suggestedAction: data.suggested_action,
1022
1064
  raw: data
1023
1065
  };
1024
1066
  }
@@ -1154,6 +1196,12 @@ function validateSignalDiscoveryParams(params) {
1154
1196
  if (query && (query.length < 2 || query.length > 2e3)) {
1155
1197
  throw new RangeError("query must contain between 2 and 2000 characters");
1156
1198
  }
1199
+ if (params.icpContext !== void 0) {
1200
+ const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
1201
+ if (icpContext.length < 2 || icpContext.length > 4e3) {
1202
+ throw new RangeError("icpContext must contain between 2 and 4000 characters");
1203
+ }
1204
+ }
1157
1205
  if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
1158
1206
  throw new RangeError("limit must be an integer between 1 and 1000");
1159
1207
  }
@@ -1178,6 +1226,7 @@ function signalDiscoveryRequestBody(params) {
1178
1226
  }
1179
1227
  return compact({
1180
1228
  query: params.query?.trim(),
1229
+ icp_context: params.icpContext?.trim(),
1181
1230
  limit: params.limit ?? 100,
1182
1231
  sources: params.sources,
1183
1232
  lookback_days: params.lookbackDays,
@@ -1203,6 +1252,8 @@ function normalizeSignalDiscoveryResult(params, data) {
1203
1252
  availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
1204
1253
  sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
1205
1254
  evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
1255
+ discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
1256
+ identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
1206
1257
  costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
1207
1258
  warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
1208
1259
  raw: data
@@ -1231,7 +1282,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
1231
1282
  guardrail.projected_cost_per_requested_company_signal_usd
1232
1283
  ),
1233
1284
  sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
1234
- costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1285
+ costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1286
+ identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
1287
+ identityCostSource: [
1288
+ "observed_zero_credit_receipts",
1289
+ "contractual_zero_cost",
1290
+ "not_used",
1291
+ "unavailable"
1292
+ ].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
1293
+ identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
1235
1294
  qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
1236
1295
  returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
1237
1296
  availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
@@ -1335,6 +1394,7 @@ function normalizedFindEmailBatchName(request) {
1335
1394
  function coreProductBatchRequestKey(path, request) {
1336
1395
  if (path === "api/v1/find-email") {
1337
1396
  return JSON.stringify([
1397
+ normalizedBatchText(request.run_id),
1338
1398
  normalizedFindEmailBatchName(request),
1339
1399
  normalizedBatchDomain(request.company_domain),
1340
1400
  normalizedBatchLinkedIn(request.linkedin_url),
@@ -1440,6 +1500,7 @@ function signalToCopyRequestBody(params) {
1440
1500
  title: params.title,
1441
1501
  campaign_offer: params.campaignOffer,
1442
1502
  research_prompt: params.researchPrompt,
1503
+ lookback_days: params.lookbackDays,
1443
1504
  signal_run_id: params.signalRunId,
1444
1505
  skip_cache: params.skipCache,
1445
1506
  enable_deep_search: params.enableDeepSearch,
@@ -1450,6 +1511,9 @@ function signalToCopyRequestBody(params) {
1450
1511
  }
1451
1512
  function validateSignalToCopyParams(params) {
1452
1513
  validateCoreProductMaxWaitMs(params.maxWaitMs);
1514
+ if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
1515
+ throw new RangeError("lookbackDays must be an integer between 1 and 365");
1516
+ }
1453
1517
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1454
1518
  const missing = [
1455
1519
  ["companyDomain", params.companyDomain],
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Signaliz,
3
3
  SignalizError
4
- } from "./chunk-MFY4KSQ6.mjs";
4
+ } from "./chunk-ENA7AVFB.mjs";
5
5
  export {
6
6
  Signaliz,
7
7
  SignalizError
@@ -40,17 +40,21 @@ var SignalizError = class extends Error {
40
40
  this.details = data.details;
41
41
  }
42
42
  get isRetryable() {
43
+ if (typeof this.details?.retry_eligible === "boolean") {
44
+ return this.details.retry_eligible;
45
+ }
43
46
  return this.errorType === "rate_limited" || this.errorType === "timeout" || this.errorType === "provider_error";
44
47
  }
45
48
  };
46
49
  function parseError(status, body) {
47
50
  if (body?.error && typeof body.error === "object") {
51
+ const nestedDetails = body.error.details && typeof body.error.details === "object" ? body.error.details : {};
48
52
  return new SignalizError({
49
53
  code: body.error.code || `HTTP_${status}`,
50
54
  message: body.error.message || `Request failed with status ${status}`,
51
55
  errorType: mapErrorType(body.error.code, status),
52
56
  retryAfter: retryAfterSeconds(body.error),
53
- details: body.error.details
57
+ details: mergePublicErrorDetails({ ...body, ...body.error }, nestedDetails)
54
58
  });
55
59
  }
56
60
  const code = typeof body?.error_code === "string" && body.error_code.trim() ? body.error_code.trim() : `HTTP_${status}`;
@@ -59,7 +63,7 @@ function parseError(status, body) {
59
63
  message: body?.message || body?.error || `Request failed with status ${status}`,
60
64
  errorType: mapErrorType(code, status),
61
65
  retryAfter: retryAfterSeconds(body),
62
- details: body?.details || publicRetryDetails(body)
66
+ details: mergePublicErrorDetails(body, body?.details)
63
67
  });
64
68
  }
65
69
  function retryAfterSeconds(body) {
@@ -71,13 +75,26 @@ function publicRetryDetails(body) {
71
75
  ...body?.retry_eligible !== void 0 ? { retry_eligible: body.retry_eligible } : {},
72
76
  ...body?.retry_after !== void 0 ? { retry_after: body.retry_after } : {},
73
77
  ...body?.retry_after_seconds !== void 0 ? { retry_after_seconds: body.retry_after_seconds } : {},
78
+ ...body?.retry_strategy !== void 0 ? { retry_strategy: body.retry_strategy } : {},
79
+ ...body?.credits_used !== void 0 ? { credits_used: body.credits_used } : {},
80
+ ...body?.credits_charged !== void 0 ? { credits_charged: body.credits_charged } : {},
81
+ ...body?.job_id !== void 0 ? { job_id: body.job_id } : {},
82
+ ...body?.verification_run_id !== void 0 ? { verification_run_id: body.verification_run_id } : {},
74
83
  ...body?.run_id !== void 0 ? { run_id: body.run_id } : {},
84
+ ...body?.suggested_action !== void 0 ? { suggested_action: body.suggested_action } : {},
75
85
  ...body?.signal_run_id !== void 0 ? { signal_run_id: body.signal_run_id } : {},
76
86
  ...body?.company_signal_run_id !== void 0 ? { company_signal_run_id: body.company_signal_run_id } : {},
77
87
  ...body?.resume_context_persisted !== void 0 ? { resume_context_persisted: body.resume_context_persisted } : {}
78
88
  };
79
89
  return Object.keys(details).length > 0 ? details : void 0;
80
90
  }
91
+ function mergePublicErrorDetails(body, explicitDetails) {
92
+ const merged = { ...publicRetryDetails(body) || {} };
93
+ if (explicitDetails && typeof explicitDetails === "object" && !Array.isArray(explicitDetails)) {
94
+ Object.assign(merged, explicitDetails);
95
+ }
96
+ return Object.keys(merged).length > 0 ? merged : void 0;
97
+ }
81
98
  function mapErrorType(code, status) {
82
99
  if (code === "RATE_LIMITED" || status === 429) return "rate_limited";
83
100
  if (code === "VALIDATION_ERROR" || code === "SIGNAL_RUN_COMPANY_MISMATCH" || status === 400) return "validation";
@@ -139,7 +156,9 @@ var HttpClient = class {
139
156
  const errBody = await res.json().catch(() => ({}));
140
157
  const err = parseError(res.status, errBody);
141
158
  if (err.isRetryable && attempt < this.maxRetries) {
142
- const wait = err.retryAfter ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
159
+ const retryWithNewKey = err.details?.retry_strategy === "new_idempotency_key" && err.details?.credits_used === 0 && err.details?.credits_charged === 0;
160
+ if (retryWithNewKey) throw err;
161
+ const wait = err.retryAfter !== void 0 ? err.retryAfter * 1e3 : Math.min(1e3 * Math.pow(2, attempt), 1e4);
143
162
  await sleep(wait);
144
163
  lastError = err;
145
164
  continue;
@@ -796,7 +815,10 @@ var Signaliz = class {
796
815
  );
797
816
  const uniqueTasks = deduplicated.uniqueItems;
798
817
  const recoverableBatch = path2 === "api/v1/find-email" ? { label: "Find Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : path2 === "api/v1/verify-email" ? { label: "Verify Email", pageSize: 500, maxWaitMs: 20 * 6e4 } : void 0;
799
- if (path2 !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25) {
818
+ const containsFindRecoveryReads = path2 === "api/v1/find-email" && uniqueTasks.some(
819
+ (task) => typeof task.request.run_id === "string" && task.request.run_id.trim()
820
+ );
821
+ if (path2 !== "api/v1/company-signals" && recoverableBatch && uniqueTasks.length > 25 && !containsFindRecoveryReads) {
800
822
  const rows = await this.runRecoverableCoreBatchJob(
801
823
  path2,
802
824
  uniqueTasks.map((task) => task.request),
@@ -922,13 +944,27 @@ function batchItemFailure(index, error, raw) {
922
944
  raw?.retry_after_seconds ?? raw?.retry_after ?? raw?.retryAfterSeconds ?? (Number.isFinite(Number(raw?.retry_after_ms)) ? Number(raw?.retry_after_ms) / 1e3 : void 0)
923
945
  );
924
946
  const retryEligible = raw?.retry_eligible ?? raw?.retryEligible;
947
+ const retryStrategy = raw?.retry_strategy ?? raw?.retryStrategy;
948
+ const creditsUsed = raw?.credits_used ?? raw?.creditsUsed;
949
+ const creditsCharged = raw?.credits_charged ?? raw?.creditsCharged;
950
+ const runId = raw?.run_id ?? raw?.runId;
951
+ const verificationRunId = raw?.verification_run_id ?? raw?.verificationRunId;
952
+ const jobId = raw?.job_id ?? raw?.jobId;
953
+ const suggestedAction = raw?.suggested_action ?? raw?.suggestedAction;
925
954
  return {
926
955
  index,
927
956
  success: false,
928
957
  error,
929
958
  ...errorCode ? { errorCode } : {},
930
959
  ...typeof retryEligible === "boolean" ? { retryEligible } : {},
931
- ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {}
960
+ ...Number.isFinite(retryAfterSeconds2) && retryAfterSeconds2 >= 0 ? { retryAfterSeconds: retryAfterSeconds2 } : {},
961
+ ...retryStrategy === "new_idempotency_key" ? { retryStrategy } : {},
962
+ ...creditsUsed === 0 ? { creditsUsed: 0 } : {},
963
+ ...creditsCharged === 0 ? { creditsCharged: 0 } : {},
964
+ ...typeof runId === "string" && runId.trim() ? { runId: runId.trim() } : {},
965
+ ...typeof verificationRunId === "string" && verificationRunId.trim() ? { verificationRunId: verificationRunId.trim() } : {},
966
+ ...typeof jobId === "string" && jobId.trim() ? { jobId: jobId.trim() } : {},
967
+ ...typeof suggestedAction === "string" && suggestedAction.trim() ? { suggestedAction: suggestedAction.trim() } : {}
932
968
  };
933
969
  }
934
970
  function recoverableJobRowFailed(row) {
@@ -943,6 +979,7 @@ function compact(input) {
943
979
  }
944
980
  function findEmailRequestBody(params) {
945
981
  return compact({
982
+ run_id: params.runId,
946
983
  company_domain: params.companyDomain,
947
984
  full_name: params.fullName,
948
985
  first_name: params.firstName,
@@ -982,6 +1019,7 @@ function normalizeCoreProductDryRunResult(data) {
982
1019
  };
983
1020
  }
984
1021
  function normalizeFindEmailResult(data) {
1022
+ const processing = String(data.status || "").toLowerCase() === "processing";
985
1023
  const failed = coreProductResponseFailed(data);
986
1024
  const rawEmail = typeof data.email === "string" && data.email.trim() ? data.email.trim() : null;
987
1025
  const email = failed ? null : rawEmail;
@@ -994,7 +1032,7 @@ function normalizeFindEmailResult(data) {
994
1032
  const verified = !explicitlyNotSendSafe && (data.is_valid === true || data.isValid === true || data.email_verified === true || data.verified_for_sending === true || ["valid", "deliverable"].includes(String(verificationStatus).toLowerCase()));
995
1033
  const verificationAgeDays = typeof data.verification_age_days === "number" && Number.isFinite(data.verification_age_days) ? data.verification_age_days : null;
996
1034
  return {
997
- success: !failed && found,
1035
+ success: processing || !failed && found,
998
1036
  found,
999
1037
  email,
1000
1038
  firstName: data.first_name,
@@ -1021,8 +1059,12 @@ function normalizeFindEmailResult(data) {
1021
1059
  liveProviderCalled: data.live_provider_called,
1022
1060
  openrouterCalled: data.openrouter_called,
1023
1061
  byoOpenrouterUsed: data.byo_openrouter_used,
1024
- error: found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1025
- errorCode: found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1062
+ error: processing || found ? void 0 : coreProductErrorMessage(data) ?? "No verified email found",
1063
+ errorCode: processing || found ? void 0 : coreProductErrorCode(data) ?? "EMAIL_NOT_FOUND",
1064
+ status: processing ? "processing" : "completed",
1065
+ runId: data.run_id,
1066
+ nextPollAfterSeconds: data.next_poll_after_seconds,
1067
+ suggestedAction: data.suggested_action,
1026
1068
  raw: data
1027
1069
  };
1028
1070
  }
@@ -1158,6 +1200,12 @@ function validateSignalDiscoveryParams(params) {
1158
1200
  if (query && (query.length < 2 || query.length > 2e3)) {
1159
1201
  throw new RangeError("query must contain between 2 and 2000 characters");
1160
1202
  }
1203
+ if (params.icpContext !== void 0) {
1204
+ const icpContext = typeof params.icpContext === "string" ? params.icpContext.trim() : "";
1205
+ if (icpContext.length < 2 || icpContext.length > 4e3) {
1206
+ throw new RangeError("icpContext must contain between 2 and 4000 characters");
1207
+ }
1208
+ }
1161
1209
  if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 1e3)) {
1162
1210
  throw new RangeError("limit must be an integer between 1 and 1000");
1163
1211
  }
@@ -1182,6 +1230,7 @@ function signalDiscoveryRequestBody(params) {
1182
1230
  }
1183
1231
  return compact({
1184
1232
  query: params.query?.trim(),
1233
+ icp_context: params.icpContext?.trim(),
1185
1234
  limit: params.limit ?? 100,
1186
1235
  sources: params.sources,
1187
1236
  lookback_days: params.lookbackDays,
@@ -1207,6 +1256,8 @@ function normalizeSignalDiscoveryResult(params, data) {
1207
1256
  availableCompanySignalCount: Number.isFinite(Number(data.available_company_signal_count)) ? Number(data.available_company_signal_count) : void 0,
1208
1257
  sourceLanes: Array.isArray(data.source_lanes) ? data.source_lanes.filter((lane) => typeof lane === "string") : void 0,
1209
1258
  evidenceValidation: data.evidence_validation && typeof data.evidence_validation === "object" ? data.evidence_validation : void 0,
1259
+ discoveryDiagnostics: data.discovery_diagnostics && typeof data.discovery_diagnostics === "object" ? data.discovery_diagnostics : void 0,
1260
+ identityResolution: data.identity_resolution && typeof data.identity_resolution === "object" ? data.identity_resolution : void 0,
1210
1261
  costGuardrail: normalizeSignalDiscoveryCostGuardrail(data.cost_guardrail),
1211
1262
  warnings: Array.isArray(data.warnings) ? data.warnings.filter((warning) => typeof warning === "string") : void 0,
1212
1263
  raw: data
@@ -1235,7 +1286,15 @@ function normalizeSignalDiscoveryCostGuardrail(value) {
1235
1286
  guardrail.projected_cost_per_requested_company_signal_usd
1236
1287
  ),
1237
1288
  sourceCostUsd: numberOrNullable(guardrail.source_cost_usd),
1238
- costSource: ["observed", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1289
+ costSource: ["observed", "catalog_preflight", "catalog_bounded", "mixed", "unavailable"].includes(String(guardrail.cost_source)) ? String(guardrail.cost_source) : void 0,
1290
+ identityResolutionCostUsd: numberOrNullable(guardrail.identity_resolution_cost_usd),
1291
+ identityCostSource: [
1292
+ "observed_zero_credit_receipts",
1293
+ "contractual_zero_cost",
1294
+ "not_used",
1295
+ "unavailable"
1296
+ ].includes(String(guardrail.identity_cost_source)) ? String(guardrail.identity_cost_source) : void 0,
1297
+ identityProviderRequests: numberOrUndefined(guardrail.identity_provider_requests),
1239
1298
  qualifiedCompanySignalCount: numberOrUndefined(guardrail.qualified_company_signal_count),
1240
1299
  returnedCompanySignalCount: numberOrUndefined(guardrail.returned_company_signal_count),
1241
1300
  availableCompanySignalCount: numberOrUndefined(guardrail.available_company_signal_count),
@@ -1339,6 +1398,7 @@ function normalizedFindEmailBatchName(request) {
1339
1398
  function coreProductBatchRequestKey(path2, request) {
1340
1399
  if (path2 === "api/v1/find-email") {
1341
1400
  return JSON.stringify([
1401
+ normalizedBatchText(request.run_id),
1342
1402
  normalizedFindEmailBatchName(request),
1343
1403
  normalizedBatchDomain(request.company_domain),
1344
1404
  normalizedBatchLinkedIn(request.linkedin_url),
@@ -1444,6 +1504,7 @@ function signalToCopyRequestBody(params) {
1444
1504
  title: params.title,
1445
1505
  campaign_offer: params.campaignOffer,
1446
1506
  research_prompt: params.researchPrompt,
1507
+ lookback_days: params.lookbackDays,
1447
1508
  signal_run_id: params.signalRunId,
1448
1509
  skip_cache: params.skipCache,
1449
1510
  enable_deep_search: params.enableDeepSearch,
@@ -1454,6 +1515,9 @@ function signalToCopyRequestBody(params) {
1454
1515
  }
1455
1516
  function validateSignalToCopyParams(params) {
1456
1517
  validateCoreProductMaxWaitMs(params.maxWaitMs);
1518
+ if (params.lookbackDays !== void 0 && (!Number.isInteger(params.lookbackDays) || params.lookbackDays < 1 || params.lookbackDays > 365)) {
1519
+ throw new RangeError("lookbackDays must be an integer between 1 and 365");
1520
+ }
1457
1521
  if (typeof params.signalRunId === "string" && params.signalRunId.trim()) return;
1458
1522
  const missing = [
1459
1523
  ["companyDomain", params.companyDomain],
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Signaliz
4
- } from "./chunk-MFY4KSQ6.mjs";
4
+ } from "./chunk-ENA7AVFB.mjs";
5
5
 
6
6
  // src/mcp-config.ts
7
7
  import * as fs from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaliz/sdk",
3
- "version": "1.0.60",
3
+ "version": "1.0.61",
4
4
  "description": "Signaliz SDK for Signals Everything, Find Email, Verify Email, Company Signal Enrichment, and Signal to Copy AI.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",