@spreadspace/sdk 0.1.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.
@@ -0,0 +1,1004 @@
1
+ import { verifyWebhook, verifyAndParseWebhook } from './webhooks.js';
2
+ export { DocumentFailedPayload, DocumentProcessedPayload, ExtractionReadyPayload, JobCompletedPayload, LoanClassifiedPayload, VerifyAndParseResult, VerifyWebhookOptions, WebhookEvent, WebhookEventType, WebhookSignatureError } from './webhooks.js';
3
+ import { Decimal } from 'decimal.js';
4
+ export { Decimal } from 'decimal.js';
5
+
6
+ /**
7
+ * Shared wire-shape and request-option types. Hand-written; the generated core
8
+ * (src/generated) is the source of truth for the full per-endpoint surface.
9
+ */
10
+ /**
11
+ * Cursor-paginated envelope. One shape across every list endpoint:
12
+ *
13
+ * { data: T[], limit: number, next_cursor: string | null }
14
+ *
15
+ * `next_cursor === null` (or absent) means end-of-stream; any non-null value is
16
+ * an opaque token to pass back as `?cursor=...`. There is NO `has_more` and NO
17
+ * `total` — stop iterating when `next_cursor` is null.
18
+ */
19
+ interface CursorPaginated<T> {
20
+ data: T[];
21
+ limit: number;
22
+ next_cursor?: string | null;
23
+ }
24
+ /** Query-param value types accepted by the transport. `undefined` is skipped. */
25
+ type QueryParams = Record<string, string | number | boolean | undefined>;
26
+ /**
27
+ * Per-request overrides accepted by every resource method. Optional; the
28
+ * transport fills sensible defaults for everything below.
29
+ */
30
+ interface RequestOptions {
31
+ /**
32
+ * Override the auto-generated idempotency key for this single non-GET
33
+ * request. Pass `null` to suppress automatic generation entirely. Omit to
34
+ * let the transport generate one (default: `crypto.randomUUID()`).
35
+ */
36
+ idempotencyKey?: string | null;
37
+ /** Override the `SpreadSpace-Version` header for this single request. */
38
+ apiVersion?: string;
39
+ /** `AbortSignal` for canceling the request. Composed with the per-attempt timeout. */
40
+ signal?: AbortSignal;
41
+ /** Override `maxRetries` for this single request. */
42
+ maxRetries?: number;
43
+ }
44
+ /**
45
+ * Internal options for the low-level `request()` method. Resource methods
46
+ * compose these on top of `RequestOptions`.
47
+ */
48
+ interface InternalRequestOptions extends RequestOptions {
49
+ /** JSON-serializable request body. Encoded as `application/json`. */
50
+ body?: unknown;
51
+ /** Query parameters appended to the URL. `undefined` values are skipped. */
52
+ query?: QueryParams;
53
+ }
54
+
55
+ /**
56
+ * HTTP transport: auth, version header, idempotency, retries, error mapping.
57
+ *
58
+ * The configured client every helper sits on top of. Hand-written, not
59
+ * generated. Responsibilities:
60
+ *
61
+ * - `Authorization: Bearer <key>` + `SpreadSpace-Version` on every API request.
62
+ * - Auto `Idempotency-Key` (uuid) on non-GET methods, suppressible per call.
63
+ * - Retry 429 + 5xx + transport errors with exponential backoff + full jitter,
64
+ * honoring `Retry-After`.
65
+ * - Map the canonical error envelope to typed errors carrying `requestId`,
66
+ * preferring the `X-Request-ID` response header.
67
+ *
68
+ * `fetch` and `sleep` are injectable so unit tests stub the network and never
69
+ * actually delay. Success bodies decode via `parseBodyWithExactMoney` so money
70
+ * fields come back as exact `Decimal`s (lossless-json + decimal.js) — the wire
71
+ * is unchanged (money stays a JSON number); only the decoded JS type differs.
72
+ * The error envelope still uses plain `JSON.parse` (no money there).
73
+ */
74
+
75
+ declare const DEFAULT_BASE_URL = "https://api.spreadspace.ai";
76
+ type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
77
+ interface TransportOptions {
78
+ /** API key. `ss_live_` -> live tenant, `ss_test_` -> sandbox tenant. */
79
+ apiKey: string;
80
+ /** Base URL. Defaults to `https://api.spreadspace.ai` (trailing slash trimmed). */
81
+ baseUrl?: string;
82
+ /** Override the SDK-pinned `SpreadSpace-Version` header. */
83
+ apiVersion?: string;
84
+ /** Per-attempt request timeout in ms. Default 60000. Retries get a fresh window. */
85
+ timeout?: number;
86
+ /**
87
+ * Max retry attempts after the initial request. Default 2. Retries on 429,
88
+ * 5xx, and transport failures; other 4xx never retry.
89
+ */
90
+ maxRetries?: number;
91
+ /** `fetch` implementation. Defaults to `globalThis.fetch`. Inject a mock in tests. */
92
+ fetch?: typeof fetch;
93
+ /** Sleep used between retries. Injectable so tests don't actually wait. */
94
+ sleep?: (ms: number) => Promise<void>;
95
+ /** Idempotency-key generator. Defaults to `crypto.randomUUID()`. */
96
+ idempotencyKeyGenerator?: () => string;
97
+ }
98
+ /** Configured HTTP client wrapping `fetch` with SpreadSpace conventions. */
99
+ declare class Transport {
100
+ /** Resolved base URL (no trailing slash). */
101
+ readonly baseUrl: string;
102
+ /** Resolved API version (the `SpreadSpace-Version` header value). */
103
+ readonly apiVersion: string;
104
+ /** Default retry ceiling. Public so helpers can construct URLs / pagers off it. */
105
+ readonly maxRetries: number;
106
+ private readonly apiKey;
107
+ private readonly timeoutMs;
108
+ private readonly fetchImpl;
109
+ private readonly sleepImpl;
110
+ private readonly idempotencyKeyGenerator;
111
+ private readonly userAgent;
112
+ constructor(options: TransportOptions);
113
+ /**
114
+ * Issue an API request and return the decoded JSON body (or `undefined` for
115
+ * an empty / 204 response). Integrators can call this directly to hit
116
+ * endpoints not yet surfaced via a typed resource.
117
+ */
118
+ request<T>(method: HttpMethod, path: string, options?: InternalRequestOptions): Promise<T>;
119
+ /**
120
+ * Single retry path shared by `request()` and `putPresigned()` (mirrors the
121
+ * Python `_send`). Issues `fetch` with a per-attempt timeout, retrying 429 +
122
+ * 5xx + transport failures on the full-jitter backoff curve, honoring
123
+ * `Retry-After`. Returns the final `Response` (success OR a non-retryable /
124
+ * retries-exhausted error status — callers decide what to do with it); throws
125
+ * `NetworkError` only when transport failures exhaust retries.
126
+ */
127
+ private send;
128
+ /**
129
+ * PUT raw bytes straight to a presigned S3 URL (out-of-band, no auth).
130
+ *
131
+ * `contentType` MUST equal the value sent when minting the URL — it is part
132
+ * of the V4 signature. No SSE headers (bucket-default KMS applies). No
133
+ * Authorization / version headers; this does not hit a SpreadSpace endpoint.
134
+ * Retries on transport/5xx like `request()`; throws `SpreadSpaceError` on a
135
+ * non-2xx S3 status.
136
+ */
137
+ putPresigned(url: string, data: BodyInit, contentType: string, options?: {
138
+ maxRetries?: number;
139
+ signal?: AbortSignal;
140
+ }): Promise<Response>;
141
+ private buildUrl;
142
+ private buildHeaders;
143
+ /**
144
+ * Next retry delay. Exponential backoff with full jitter, floored by
145
+ * `Retry-After` when the server provided one (never retry faster than asked).
146
+ *
147
+ * base = min(maxDelay, baseDelay * 2^attempt)
148
+ * delay = random(0, base)
149
+ *
150
+ * Full jitter (AWS-recommended) minimizes thundering-herd across clients.
151
+ */
152
+ private computeRetryDelay;
153
+ private parseSuccessBody;
154
+ private buildErrorFromResponse;
155
+ }
156
+
157
+ /**
158
+ * Cursor pagination over `CursorPaginated<T>` list envelopes.
159
+ *
160
+ * The API pages with `{ data: T[], limit: number, next_cursor: string | null }`;
161
+ * the ONLY end-of-stream signal is `next_cursor === null` (no `has_more`, no
162
+ * `total`). One route diverges (`GET /api/async-operations`: items under
163
+ * `operations`), so `itemsKey` is overridable. The returned pager is an
164
+ * `AsyncIterable<T>` that yields items lazily, auto-fetching pages until the
165
+ * cursor is exhausted, plus `.pages()` (envelope-at-a-time) and `.toArray()`.
166
+ */
167
+
168
+ /**
169
+ * Lazy pager over a cursor-paginated list endpoint. Three ways to consume:
170
+ *
171
+ * 1. **`for await`** — every item across all pages (the common case).
172
+ * 2. **`.pages()`** — one `CursorPaginated<T>` envelope per HTTP round-trip.
173
+ * 3. **`.toArray()`** — eagerly drain into a single array (bounded queries only).
174
+ */
175
+ interface AsyncPager<T> extends AsyncIterable<T> {
176
+ /** Page-by-page async iterator; each value is a full envelope. */
177
+ pages(): AsyncIterableIterator<CursorPaginated<T>>;
178
+ /** Eagerly drain every page into one array. Throws if any request fails. */
179
+ toArray(): Promise<T[]>;
180
+ }
181
+ /** Options for {@link paginate}. */
182
+ interface PaginateOptions {
183
+ /** Base query params, merged into every page request. Never mutated. */
184
+ params?: QueryParams;
185
+ /** Response key holding the item array. `"data"`, or `"operations"` for async-ops. */
186
+ itemsKey?: string;
187
+ /** Query param carrying the opaque next-page token. Default `"cursor"`. */
188
+ cursorParam?: string;
189
+ /** Override the `SpreadSpace-Version` header for every page request. */
190
+ apiVersion?: string;
191
+ }
192
+ /** Build a pager that walks a cursor-paginated list endpoint. */
193
+ declare function paginate<T>(transport: Transport, path: string, options?: PaginateOptions): AsyncPager<T>;
194
+
195
+ /**
196
+ * Public error hierarchy for the SpreadSpace SDK.
197
+ *
198
+ * Any non-2xx API response is thrown as one of the subclasses below so
199
+ * integrators pattern-match on the type without parsing bodies. Shape mirrors
200
+ * the canonical envelope:
201
+ *
202
+ * { error: { type, message, request_id, details? } }
203
+ *
204
+ * `requestId` is preserved on every thrown error (prefer the `X-Request-ID`
205
+ * response header over the body's `request_id`). Transport-level failures
206
+ * surface as `NetworkError`.
207
+ */
208
+ interface SpreadSpaceErrorParams {
209
+ type: string;
210
+ message: string;
211
+ statusCode: number;
212
+ requestId?: string;
213
+ rawBody?: unknown;
214
+ details?: Record<string, string>;
215
+ /** `Retry-After` seconds echoed from a 429/503, when present. */
216
+ retryAfter?: number;
217
+ }
218
+ /** Base class for every API-shaped error. */
219
+ declare class SpreadSpaceError extends Error {
220
+ /** Stable machine code from `error.type` (match on this, never `message`). */
221
+ readonly type: string;
222
+ /** HTTP status code. */
223
+ readonly statusCode: number;
224
+ /** `X-Request-ID` echoed from the server. Quote in support tickets. */
225
+ readonly requestId?: string;
226
+ /** Raw response body for debugging — parsed JSON or a string. */
227
+ readonly rawBody?: unknown;
228
+ /** Optional structured details (e.g. a field name on a validation error). */
229
+ readonly details?: Record<string, string>;
230
+ /** `Retry-After` seconds, when the server provided one. */
231
+ readonly retryAfter?: number;
232
+ constructor(params: SpreadSpaceErrorParams);
233
+ }
234
+ /** 400 Bad Request — typically schema validation or an invalid cursor/version. */
235
+ declare class InvalidRequestError extends SpreadSpaceError {
236
+ }
237
+ /** 401 Unauthorized — missing, expired, or invalid API key. */
238
+ declare class AuthenticationError extends SpreadSpaceError {
239
+ }
240
+ /** 403 Forbidden — authenticated but not allowed (scope, plan, mode, PII claim). */
241
+ declare class PermissionError extends SpreadSpaceError {
242
+ }
243
+ /** 404 Not Found. */
244
+ declare class NotFoundError extends SpreadSpaceError {
245
+ }
246
+ /** 409 Conflict — idempotency-key mismatch or cancel-on-terminal-operation. */
247
+ declare class ConflictError extends SpreadSpaceError {
248
+ }
249
+ /**
250
+ * 429 Too Many Requests. The SDK retries automatically up to `maxRetries`,
251
+ * honoring `Retry-After`; this surfaces only when retries are exhausted.
252
+ */
253
+ declare class RateLimitError extends SpreadSpaceError {
254
+ }
255
+ /** 5xx Server Error. Retries are exhausted by the time this surfaces. */
256
+ declare class ServerError extends SpreadSpaceError {
257
+ }
258
+ /**
259
+ * Transport-level failure — DNS, TCP reset, fetch abort, or a JSON parse
260
+ * failure on a 2xx response. Distinct from API-shaped errors.
261
+ */
262
+ declare class NetworkError extends Error {
263
+ readonly cause?: unknown;
264
+ constructor(message: string, cause?: unknown);
265
+ }
266
+ /**
267
+ * Map an HTTP status + canonical `error.type` to an error class. Status is the
268
+ * primary signal (a bodyless 500 still becomes `ServerError`); `type` is
269
+ * accepted for future sharpening at the same status. Mapping:
270
+ * 400->InvalidRequest, 401->Authentication, 403->Permission, 404->NotFound,
271
+ * 409->Conflict, 429->RateLimit, >=500->Server. Unlisted 4xx (e.g. the 402
272
+ * billing wall) -> the generic `SpreadSpaceError` base — NOT InvalidRequest,
273
+ * which would misrepresent them as 400-style validation errors. Mirrors the
274
+ * Python `classify` fallthrough (unlisted 4xx -> APIStatusError base).
275
+ */
276
+ declare function classifyError(statusCode: number, _type: string | undefined): typeof SpreadSpaceError;
277
+
278
+ /**
279
+ * Async operations: extraction-export create + poll-to-terminal.
280
+ *
281
+ * Long-running server work (e.g. extraction export) is exposed as an *async
282
+ * operation*: a POST enqueues it and returns an envelope you poll via
283
+ * `GET /api/async-operations/{id}` until terminal. Create / get / cancel all
284
+ * return the same `AsyncOperationResponse` envelope, modeled as `AsyncOperation`.
285
+ *
286
+ * Terminal statuses are `succeeded` / `failed` / `cancelled` (double-L); the
287
+ * non-terminal ones are `queued` / `running` (there is no "pending").
288
+ * `AsyncOperationHandle.wait` polls with light backoff until terminal, rejecting
289
+ * on `failed` / timeout. The clock and sleep are injectable so tests never wait.
290
+ *
291
+ * New to TS — the node-sdk has no waiter; ported from the Python reference.
292
+ */
293
+
294
+ /** Operation statuses considered terminal — `wait()` stops on these. */
295
+ declare const TERMINAL_STATUSES: Set<string>;
296
+ /** Minimal transport surface this helper needs. `Transport.request` satisfies it. */
297
+ type OperationsTransport = Pick<Transport, 'request'>;
298
+ /** Mapped `AsyncOperationResponse` envelope. Raw body kept on `.raw` for forward-compat. */
299
+ interface AsyncOperation {
300
+ operationId: string;
301
+ kind: string;
302
+ status: string;
303
+ progress: {
304
+ completed?: number;
305
+ total?: number;
306
+ };
307
+ result: unknown;
308
+ resultUrl?: string;
309
+ resultExpiresAt?: string;
310
+ errorCode?: string;
311
+ errorMessage?: string;
312
+ createdAt?: string;
313
+ startedAt?: string;
314
+ completedAt?: string;
315
+ expiresAt?: string;
316
+ links: {
317
+ self?: string;
318
+ cancel?: string;
319
+ };
320
+ /** Full decoded body, for fields not yet mapped. */
321
+ raw: Record<string, unknown>;
322
+ }
323
+ /** Body for `POST /api/async-operations/extraction_export`. All fields optional. */
324
+ interface ExtractionExportParams {
325
+ borrowerId?: string;
326
+ loanId?: string;
327
+ documentIds?: string[];
328
+ format?: string;
329
+ deliveryMode?: string;
330
+ }
331
+ /** Tuning + injected clock for `AsyncOperationHandle.wait`. */
332
+ interface WaitOptions$1 {
333
+ /** Give up after this many ms (default 300000). Rejects with `AsyncOperationTimeout`. `null` = forever (parity with Python `timeout=None`). */
334
+ timeoutMs?: number | null;
335
+ /** Initial poll interval in ms (default 2000); backs off ×2 up to a 10s cap. */
336
+ pollIntervalMs?: number;
337
+ /** Sleep impl. Injectable so tests don't actually wait. Defaults to `setTimeout`. */
338
+ sleep?: (ms: number) => Promise<void>;
339
+ /** Monotonic clock in ms. Injectable for deterministic timeout tests. Defaults to `Date.now`. */
340
+ now?: () => number;
341
+ }
342
+ /** True when the operation reached a terminal status. */
343
+ declare function isTerminal(operation: AsyncOperation): boolean;
344
+ /**
345
+ * A waited-on operation ended in status `failed`. Carries the operation's
346
+ * `error_code` (as `type`) / `error_message`, plus the terminal op on `.operation`.
347
+ */
348
+ declare class AsyncOperationError extends SpreadSpaceError {
349
+ readonly operation: AsyncOperation;
350
+ constructor(operation: AsyncOperation);
351
+ }
352
+ /** `wait()` exceeded its timeout before the operation reached a terminal state. */
353
+ declare class AsyncOperationTimeout extends SpreadSpaceError {
354
+ readonly operation: AsyncOperation;
355
+ constructor(message: string, operation: AsyncOperation);
356
+ }
357
+ /** Live handle to one async operation: refresh, cancel, wait-to-terminal. */
358
+ declare class AsyncOperationHandle {
359
+ private readonly transport;
360
+ private op;
361
+ constructor(transport: OperationsTransport, operation: AsyncOperation);
362
+ get id(): string;
363
+ /** Last-known state — does not hit the network. */
364
+ get operation(): AsyncOperation;
365
+ /** GET the operation once and cache the result. */
366
+ refresh(options?: RequestOptions): Promise<AsyncOperation>;
367
+ /** POST the cancel route and cache the returned state. */
368
+ cancel(options?: RequestOptions): Promise<AsyncOperation>;
369
+ /**
370
+ * Poll until terminal. Resolves the `AsyncOperation` on `succeeded` /
371
+ * `cancelled`; rejects with `AsyncOperationError` on `failed`, or
372
+ * `AsyncOperationTimeout` if `timeoutMs` elapses while still non-terminal.
373
+ * `sleep` and `now` are injectable so tests never actually wait.
374
+ */
375
+ wait(options?: WaitOptions$1): Promise<AsyncOperation>;
376
+ }
377
+ /**
378
+ * Enqueue an extraction export and return a handle to poll/cancel it. All
379
+ * fields are optional; `undefined` values are omitted from the body rather than
380
+ * sent as wire nulls.
381
+ *
382
+ * POST /api/async-operations/extraction_export
383
+ */
384
+ declare function createExtractionExport(transport: OperationsTransport, params?: ExtractionExportParams, options?: RequestOptions): Promise<AsyncOperationHandle>;
385
+ /**
386
+ * Fetch one async operation by id.
387
+ *
388
+ * GET /api/async-operations/{operationId}
389
+ */
390
+ declare function getOperation(transport: OperationsTransport, operationId: string, options?: RequestOptions): Promise<AsyncOperation>;
391
+ /**
392
+ * Request cancellation. Cancelling a terminal (non-cancelled) op → 409
393
+ * (`ConflictError`); cancelling an already-cancelled op is idempotent (200).
394
+ *
395
+ * POST /api/async-operations/{operationId}/cancel
396
+ */
397
+ declare function cancelOperation(transport: OperationsTransport, operationId: string, options?: RequestOptions): Promise<AsyncOperation>;
398
+
399
+ /**
400
+ * Document upload: mint presigned URL -> PUT bytes to S3 -> confirm -> poll.
401
+ *
402
+ * Three-step dance so raw bytes never transit a SpreadSpace endpoint body — they
403
+ * go straight to S3:
404
+ *
405
+ * 1. `POST /api/documents/presigned-url` mints a short-lived V4-signed S3 URL
406
+ * plus the `jobId` that tracks processing. The response is **camelCase** (the
407
+ * DTO carries no `[JsonPropertyName]`); `parsePresignedUrl` reads camelCase
408
+ * and falls back to snake_case defensively. This endpoint can also return
409
+ * **402** (`SubscriptionRequiredResponse`, a non-canonical `{error,message}`
410
+ * body) when billing is inactive — the transport raises; we let it propagate
411
+ * so callers see the billing wall rather than a hang.
412
+ * 2. HTTP `PUT` the raw bytes to that URL. The `Content-Type` MUST equal the one
413
+ * sent in step 1 — it is baked into the V4 signature, so a mismatch is a 403.
414
+ * 3. `POST /api/documents/{jobId}/confirm-upload` kicks off the Step Functions
415
+ * pipeline; the job goes `PROCESSING`.
416
+ *
417
+ * `JobHandle.wait` then polls `GET /api/documents/{jobId}/status` to a terminal
418
+ * job status. Terminal job statuses are `COMPLETED` / `FAILED` (per the API's
419
+ * `JobsController.IsTerminalStatus`); `PENDING` / `PROCESSING` are in-flight.
420
+ * (`REJECTED` is a per-*document* outcome, not a job status — a FAILED job is what
421
+ * surfaces as "rejected" in the UI.)
422
+ */
423
+
424
+ declare const TERMINAL_JOB_STATUSES: ReadonlySet<string>;
425
+ /** A minted upload target (`PresignedUrlResponse`). */
426
+ interface PresignedUrl {
427
+ jobId: string;
428
+ uploadUrl: string;
429
+ s3Key: string;
430
+ expiresInSeconds?: number;
431
+ }
432
+ /**
433
+ * Parse the presigned-url response, tolerating camelCase or snake_case keys.
434
+ *
435
+ * The live DTO is camelCase (`jobId`, `uploadUrl`, ...); we read those first and
436
+ * fall back to snake_case so the helper survives a future wire rename.
437
+ */
438
+ declare function parsePresignedUrl(body: Record<string, unknown>): PresignedUrl;
439
+ /**
440
+ * Upload failed, or the job reached a terminal failure status. Carries the
441
+ * last-known status body on `.statusBody` when a waited-on job ended `FAILED`.
442
+ */
443
+ declare class UploadError extends SpreadSpaceError {
444
+ readonly statusBody?: Record<string, unknown>;
445
+ constructor(message: string, statusBody?: Record<string, unknown>);
446
+ }
447
+ /** `wait()` exceeded its timeout before the job reached a terminal status. */
448
+ declare class UploadTimeout extends SpreadSpaceError {
449
+ readonly statusBody?: Record<string, unknown>;
450
+ constructor(message: string, statusBody?: Record<string, unknown>);
451
+ }
452
+ /** Options for {@link JobHandle.wait}. Clock and sleep are injectable for tests. */
453
+ interface WaitOptions {
454
+ /** Overall budget in ms before raising {@link UploadTimeout}. Default 600000. `null` = forever. */
455
+ timeoutMs?: number | null;
456
+ /** Delay between status polls, in ms. Default 3000. */
457
+ pollIntervalMs?: number;
458
+ /** Terminal statuses that end the poll. Default {@link TERMINAL_JOB_STATUSES}. */
459
+ terminalStatuses?: ReadonlySet<string>;
460
+ /** Sleep used between polls. Injectable so tests don't actually wait. */
461
+ sleep?: (ms: number) => Promise<void>;
462
+ /** Monotonic clock (ms). Injectable so tests don't actually wait. */
463
+ now?: () => number;
464
+ }
465
+ /** Live handle to one upload job: fetch status, wait-to-terminal. */
466
+ declare class JobHandle {
467
+ private readonly transport;
468
+ readonly id: string;
469
+ private status_?;
470
+ private raw;
471
+ constructor(transport: Transport, id: string, status?: string, raw?: Record<string, unknown>);
472
+ /** GET the status route once; cache and return the decoded body. */
473
+ status(): Promise<Record<string, unknown>>;
474
+ /**
475
+ * Poll status until terminal; return the final status body.
476
+ *
477
+ * Raises {@link UploadError} if the job ends in a failure status (`FAILED`),
478
+ * {@link UploadTimeout} if `timeoutMs` elapses while the job is still
479
+ * non-terminal. Clock and sleep are injectable so tests never actually wait.
480
+ */
481
+ wait(options?: WaitOptions): Promise<Record<string, unknown>>;
482
+ }
483
+ /**
484
+ * Accepted upload sources. Node and web friendly:
485
+ * - `string` path -> read via `node:fs`, derive name + content-type
486
+ * - `Uint8Array` / `Buffer` -> `fileName` + `contentType` required
487
+ * - `Blob` / `File` -> uses `.name` / `.type` when present
488
+ * - `Readable` (or any async/sync iterable of chunks) -> collected to bytes;
489
+ * `fileName` + `contentType` required
490
+ */
491
+ type UploadSource = string | Uint8Array | Blob | NodeJS.ReadableStream | AsyncIterable<Uint8Array | string> | Iterable<Uint8Array | string>;
492
+ /** Options for {@link uploadDocument}. */
493
+ interface UploadOptions {
494
+ /** Overrides the derived file name. Required for raw bytes / streams. <=255 chars. */
495
+ fileName?: string;
496
+ /** Overrides the derived content type. Required for raw bytes / streams. */
497
+ contentType?: string;
498
+ /** Byte length sent to the server. Defaults to the resolved payload length. */
499
+ fileSize?: number;
500
+ /** Associate the upload with a borrower. */
501
+ borrowerId?: string;
502
+ /** Associate the upload with a loan. */
503
+ loanId?: string;
504
+ /** sha256 hex digest for server-side dedup. Computed from bytes when omitted. */
505
+ contentHash?: string;
506
+ /** Compute + send a sha256 digest when `contentHash` is absent. Default true. */
507
+ computeHash?: boolean;
508
+ /** Marks the upload as a sample document in confirm-upload. */
509
+ sampleDocument?: string;
510
+ /** Poll to a terminal job status before returning. Default false. */
511
+ wait?: boolean;
512
+ /** Forwarded to {@link JobHandle.wait} when `wait` is true. */
513
+ waitOptions?: WaitOptions;
514
+ /** `AbortSignal` propagated to every API + S3 request. */
515
+ signal?: AbortSignal;
516
+ }
517
+ /**
518
+ * Upload a document: presigned-url -> S3 PUT -> confirm-upload.
519
+ *
520
+ * `file` may be a path string, raw `Uint8Array`/`Buffer`, a `Blob`/`File`, or a
521
+ * `Readable`/iterable of chunks. For raw bytes and streams, `fileName` and
522
+ * `contentType` are required; otherwise they are derived from the path /
523
+ * `Blob.name` + a small extension map.
524
+ *
525
+ * When `computeHash` is true and no `contentHash` is given, a sha256 hex digest of
526
+ * the bytes is computed and sent (the server uses it for dedup). `fileSize`
527
+ * defaults to the byte length.
528
+ *
529
+ * Returns a {@link JobHandle} for the `PROCESSING` job. With `wait: true`, also
530
+ * polls to a terminal job status before returning.
531
+ *
532
+ * Throws: `TypeError` for an unsupported `file`; `Error` when a required
533
+ * `fileName`/`contentType` is missing; whatever the transport raises on a non-2xx
534
+ * — e.g. the **402** billing wall on `presigned-url`.
535
+ */
536
+ declare function uploadDocument(transport: Transport, file: UploadSource, options?: UploadOptions): Promise<JobHandle>;
537
+
538
+ /**
539
+ * `client.webhooks` — webhook endpoint registration management plus the
540
+ * signature-verification helpers surfaced as static methods for discoverability.
541
+ *
542
+ * The verification helpers are also exported as bare functions from
543
+ * `@spreadspace/sdk/webhooks` for tree-shakeable import in webhook-receiver
544
+ * Lambdas (where the full client would otherwise pull in the resource layer).
545
+ */
546
+
547
+ /** Filters for {@link WebhooksResource.list} / {@link WebhooksResource.deliveries}. */
548
+ interface WebhookListParams {
549
+ /** Page size hint forwarded to the server. */
550
+ limit?: number;
551
+ /** Override the `SpreadSpace-Version` header for these page requests. */
552
+ apiVersion?: string;
553
+ }
554
+ /** `client.webhooks` — manage webhook endpoints (`/api/admin/webhooks/*`). */
555
+ declare class WebhooksResource {
556
+ private readonly transport;
557
+ /**
558
+ * Verify a `SpreadSpace-Signature` header against a body and signing secret.
559
+ * Throws `WebhookSignatureError` on any failure.
560
+ */
561
+ static readonly verifySignature: typeof verifyWebhook;
562
+ /**
563
+ * Verify a webhook signature and JSON-parse the body into a typed
564
+ * `WebhookEvent`. Throws `WebhookSignatureError` on signature failure or
565
+ * invalid JSON.
566
+ */
567
+ static readonly verifyAndParse: typeof verifyAndParseWebhook;
568
+ constructor(transport: Transport);
569
+ /** Low-level request shim onto the shared transport. */
570
+ private request;
571
+ /**
572
+ * List configured webhook endpoints for this organization.
573
+ *
574
+ * GET /api/admin/webhooks
575
+ */
576
+ list<T = Record<string, unknown>>(params?: WebhookListParams): AsyncPager<T>;
577
+ /**
578
+ * Retrieve a single webhook endpoint.
579
+ *
580
+ * GET /api/admin/webhooks/{id}
581
+ */
582
+ retrieve<T = unknown>(endpointId: string, options?: RequestOptions): Promise<T>;
583
+ /**
584
+ * Create a new webhook endpoint. The response carries the plaintext signing
585
+ * secret exactly once — store it server-side, do not log it.
586
+ *
587
+ * POST /api/admin/webhooks
588
+ */
589
+ create<T = unknown>(params: Record<string, unknown>, options?: RequestOptions): Promise<T>;
590
+ /**
591
+ * Update endpoint metadata (URL, subscribed event types, enabled flag).
592
+ *
593
+ * PATCH /api/admin/webhooks/{id}
594
+ */
595
+ update<T = unknown>(endpointId: string, params: Record<string, unknown>, options?: RequestOptions): Promise<T>;
596
+ /**
597
+ * Delete a webhook endpoint.
598
+ *
599
+ * DELETE /api/admin/webhooks/{id}
600
+ */
601
+ delete<T = unknown>(endpointId: string, options?: RequestOptions): Promise<T>;
602
+ /**
603
+ * Rotate an endpoint's signing secret. The old secret stays valid for a grace
604
+ * window so in-flight deliveries aren't dropped. Returns the new plaintext
605
+ * secret exactly once.
606
+ *
607
+ * POST /api/admin/webhooks/{id}/rotate
608
+ */
609
+ rotateSecret<T = unknown>(endpointId: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<T>;
610
+ /**
611
+ * Iterate delivery attempts for an endpoint.
612
+ *
613
+ * GET /api/admin/webhooks/{id}/deliveries
614
+ */
615
+ deliveries<T = Record<string, unknown>>(endpointId: string, params?: WebhookListParams): AsyncPager<T>;
616
+ /**
617
+ * Manually replay a delivery (e.g. after fixing a downstream outage).
618
+ *
619
+ * POST /api/admin/webhooks/{id}/deliveries/{deliveryId}/replay
620
+ */
621
+ replayDelivery<T = unknown>(endpointId: string, deliveryId: string, params?: Record<string, unknown>, options?: RequestOptions): Promise<T>;
622
+ }
623
+
624
+ /**
625
+ * `client.embed` — mint short-lived embed credentials scoped to a single loan.
626
+ *
627
+ * Mint server-side with the integrator's `embed:write` API key, then hand the
628
+ * resulting `embed_token` (or signed iframe URL) to the browser to bootstrap the
629
+ * SpreadSpace review widget. The minted `ss_embed_` token is capped to a closed
630
+ * read-only allowlist (`documents:read`, `extractions:read`, `spreads:read`) and
631
+ * is locked to its `loan_id` — it cannot reach other loans or mint further tokens.
632
+ *
633
+ * Wire is snake_case throughout. Source of truth:
634
+ * `api/src/Controllers/EmbedSessionsController.cs`.
635
+ */
636
+
637
+ /** Body for {@link EmbedSessionsResource.create}. */
638
+ interface CreateEmbedSessionParams {
639
+ /** The loan the minted token is locked to. Required. */
640
+ loan_id: string;
641
+ /** Subset of the minting key's scopes; capped to the embed allowlist. Omit to inherit. */
642
+ scopes?: string[];
643
+ /** Token lifetime. Default 3600 (1h); must be > 0; cap 86400 (24h). */
644
+ expires_in_seconds?: number;
645
+ }
646
+ /** `POST /api/embed/sessions` response shape. */
647
+ interface EmbedSessionResult {
648
+ /** The `ss_embed_*` bearer — shown ONCE, no retrieval endpoint. */
649
+ embed_token: string;
650
+ session_id: string;
651
+ /** RFC3339 token expiry. */
652
+ expires_at: string;
653
+ loan_id: string;
654
+ borrower_id: string;
655
+ /** The granted scope subset. */
656
+ scopes: string[];
657
+ }
658
+ /** Body for {@link EmbedIframeUrlsResource.create}. */
659
+ interface CreateEmbedIframeUrlParams {
660
+ /** The loan the handle/token is locked to. Required. */
661
+ loan_id: string;
662
+ /** Embed surface key (e.g. `"spreading"`), validated against the server allowlist. Required. */
663
+ surface: string;
664
+ /** Subset of the minting key's scopes; capped to the embed allowlist. */
665
+ scopes?: string[];
666
+ /** Handle lifetime. Default 60; must be > 0; cap 300 (5 min). */
667
+ handle_lifetime_seconds?: number;
668
+ /** Minted-token lifetime after exchange. Default 3600; must be > 0; cap 86400 (24h). */
669
+ token_lifetime_seconds?: number;
670
+ }
671
+ /** `POST /api/embed/iframe-urls` response shape. */
672
+ interface EmbedIframeUrlResult {
673
+ /** Drop straight into `<iframe src>`. */
674
+ signed_url: string;
675
+ /** Correlation id for the integrator's audit log. */
676
+ handle_id: string;
677
+ /** RFC3339 HANDLE expiry — not the token expiry. */
678
+ expires_at: string;
679
+ surface: string;
680
+ loan_id: string;
681
+ borrower_id: string;
682
+ scopes: string[];
683
+ }
684
+ /** `client.embed.sessions` — mint and revoke embed-session tokens. */
685
+ declare class EmbedSessionsResource {
686
+ private readonly transport;
687
+ constructor(transport: Transport);
688
+ private request;
689
+ /**
690
+ * Mint an embed-session token for a loan.
691
+ *
692
+ * POST /api/embed/sessions
693
+ *
694
+ * Server skips idempotency (`[SkipIdempotency]`); the SDK still sends the
695
+ * auto-generated `Idempotency-Key` header (harmless — the server ignores it).
696
+ * Returns the `ss_embed_*` token plus session metadata (shown once).
697
+ */
698
+ create<T = EmbedSessionResult>(params: CreateEmbedSessionParams, options?: RequestOptions): Promise<T>;
699
+ /**
700
+ * Revoke an embed session early — useful when the integrator's UI flow ends
701
+ * before the natural expiry, or when reissuing after a logout. Honors
702
+ * `Idempotency-Key`. Resolves to `void` on the `204 No Content`.
703
+ *
704
+ * DELETE /api/embed/sessions/{sessionId}
705
+ */
706
+ revoke<T = void>(sessionId: string, options?: RequestOptions): Promise<T>;
707
+ }
708
+ /** `client.embed.iframeUrls` — mint signed iframe-URL handles. */
709
+ declare class EmbedIframeUrlsResource {
710
+ private readonly transport;
711
+ constructor(transport: Transport);
712
+ private request;
713
+ /**
714
+ * Mint a signed-URL handle to drop into an `<iframe src>`. The browser then
715
+ * exchanges the handle (single-use) for an embed token at the iframe origin.
716
+ *
717
+ * POST /api/embed/iframe-urls
718
+ */
719
+ create<T = EmbedIframeUrlResult>(params: CreateEmbedIframeUrlParams, options?: RequestOptions): Promise<T>;
720
+ }
721
+ /**
722
+ * Top-level `client.embed` namespace. Surfaces the `sessions` and `iframeUrls`
723
+ * sub-resources.
724
+ */
725
+ declare class EmbedResource {
726
+ /** `embed.sessions` — mint/revoke embed-session tokens. */
727
+ readonly sessions: EmbedSessionsResource;
728
+ /** `embed.iframeUrls` — mint signed iframe-URL handles. */
729
+ readonly iframeUrls: EmbedIframeUrlsResource;
730
+ constructor(transport: Transport);
731
+ }
732
+
733
+ /**
734
+ * The `SpreadSpace` client facade.
735
+ *
736
+ * Thin ergonomic namespaces over the transport + helpers. These wrap the
737
+ * cross-cutting flows a generator can't produce (cursor pagination, the async
738
+ * operation waiter, the three-step upload sequence). A fully typed
739
+ * method-per-endpoint surface is expected to come from a generated core in CI;
740
+ * until then `request()` is the escape hatch to any endpoint not yet wrapped.
741
+ *
742
+ * Constructed from `{ apiKey?, baseUrl?, apiVersion?, timeoutMs?, maxRetries?,
743
+ * fetch? }`. An `ss_test_` key routes to the isolated sandbox tenant; `ss_live_`
744
+ * operates on real workspace data. Both hit the same base URL.
745
+ *
746
+ * Money note: amounts decode to a JS `number` (TS has no decimal type). See the
747
+ * README precision caveat — do not treat them as exact decimals.
748
+ */
749
+
750
+ /** Constructor options for {@link SpreadSpace}. All optional; sensible defaults. */
751
+ interface SpreadSpaceOptions {
752
+ /**
753
+ * API key. `ss_live_` -> live tenant, `ss_test_` -> sandbox tenant. Falls back
754
+ * to `SPREADSPACE_API_KEY` when omitted.
755
+ */
756
+ apiKey?: string;
757
+ /** Base URL. Defaults to `https://api.spreadspace.ai` (trailing slash trimmed). */
758
+ baseUrl?: string;
759
+ /** Override the SDK-pinned `SpreadSpace-Version` header (per-call overridable too). */
760
+ apiVersion?: string;
761
+ /** Per-attempt request timeout in ms. Default 60000. Retries get a fresh window. */
762
+ timeoutMs?: number;
763
+ /** Max retry attempts after the initial request. Default 2. */
764
+ maxRetries?: number;
765
+ /** `fetch` implementation. Defaults to `globalThis.fetch`. Inject a mock in tests. */
766
+ fetch?: typeof fetch;
767
+ /**
768
+ * Pre-built transport. When supplied, the connection options above are
769
+ * ignored — used by tests to share a stubbed transport with the helpers.
770
+ */
771
+ transport?: Transport;
772
+ }
773
+ /** Filters for {@link SpreadSpace.borrowers}.list. */
774
+ interface ListBorrowersParams {
775
+ /** Restrict to borrowers in the intake/triage queue when set. */
776
+ intake?: boolean;
777
+ /** Page size hint forwarded to the server. */
778
+ limit?: number;
779
+ /** Override the `SpreadSpace-Version` header for these page requests. */
780
+ apiVersion?: string;
781
+ }
782
+ /** Filters for {@link SpreadSpace.loans}.list. */
783
+ interface ListLoansParams {
784
+ /** Scope to one borrower (`GET /api/borrowers/{id}/loans`); omit for all loans. */
785
+ borrowerId?: string;
786
+ /** Page size hint forwarded to the server. */
787
+ limit?: number;
788
+ /** Override the `SpreadSpace-Version` header for these page requests. */
789
+ apiVersion?: string;
790
+ }
791
+ /** Filters for {@link SpreadSpace.jobs}.list. */
792
+ interface ListJobsParams {
793
+ /** Page size hint forwarded to the server. */
794
+ limit?: number;
795
+ /** Override the `SpreadSpace-Version` header for these page requests. */
796
+ apiVersion?: string;
797
+ }
798
+ /** Filters for {@link SpreadSpace.asyncOperations}.list. */
799
+ interface ListAsyncOperationsParams {
800
+ /** Filter by operation kind (e.g. `extraction_export`). */
801
+ kind?: string;
802
+ /** Filter by status (`queued` / `running` / `succeeded` / `failed` / `cancelled`). */
803
+ status?: string;
804
+ /** Page size hint forwarded to the server. */
805
+ limit?: number;
806
+ /** Override the `SpreadSpace-Version` header for these page requests. */
807
+ apiVersion?: string;
808
+ }
809
+ /** `client.borrowers` — list borrowers. */
810
+ declare class BorrowersResource {
811
+ private readonly transport;
812
+ constructor(transport: Transport);
813
+ /** Paginate `GET /api/borrowers`. Lazy async-iterable of borrower records. */
814
+ list<T = Record<string, unknown>>(params?: ListBorrowersParams): AsyncPager<T>;
815
+ }
816
+ /** `client.loans` — list loans, optionally scoped to a borrower. */
817
+ declare class LoansResource {
818
+ private readonly transport;
819
+ constructor(transport: Transport);
820
+ /**
821
+ * Paginate loans. With `borrowerId`, hits `GET /api/borrowers/{id}/loans`;
822
+ * otherwise the org-wide `GET /api/loans`.
823
+ */
824
+ list<T = Record<string, unknown>>(params?: ListLoansParams): AsyncPager<T>;
825
+ }
826
+ /** `client.jobs` — list document-package jobs. */
827
+ declare class JobsResource {
828
+ private readonly transport;
829
+ constructor(transport: Transport);
830
+ /** Paginate `GET /api/jobs`. */
831
+ list<T = Record<string, unknown>>(params?: ListJobsParams): AsyncPager<T>;
832
+ }
833
+ /** `client.asyncOperations` — list / get / cancel long-running operations. */
834
+ declare class AsyncOperationsResource {
835
+ private readonly transport;
836
+ constructor(transport: Transport);
837
+ /**
838
+ * Paginate `GET /api/async-operations`. Divergent envelope: items live under
839
+ * `operations` (no `data`/`limit`), so the items key is overridden here.
840
+ */
841
+ list<T = Record<string, unknown>>(params?: ListAsyncOperationsParams): AsyncPager<T>;
842
+ /** `GET /api/async-operations/{id}` — fetch one operation's current state. */
843
+ get(operationId: string, options?: RequestOptions): Promise<AsyncOperation>;
844
+ /**
845
+ * `POST /api/async-operations/{id}/cancel`. Cancelling a terminal
846
+ * (non-cancelled) op -> 409 (`ConflictError`); cancelling an already-cancelled
847
+ * op is idempotent (200).
848
+ */
849
+ cancel(operationId: string, options?: RequestOptions): Promise<AsyncOperation>;
850
+ }
851
+ /** `client.exports` — create extraction exports and poll their operations. */
852
+ declare class ExportsResource {
853
+ private readonly transport;
854
+ constructor(transport: Transport);
855
+ /**
856
+ * `POST /api/async-operations/extraction_export`. All fields optional; `null`/
857
+ * `undefined` are omitted from the body. Returns a handle whose `.wait()` polls
858
+ * the operation to a terminal state.
859
+ */
860
+ create(params?: ExtractionExportParams, options?: RequestOptions): Promise<AsyncOperationHandle>;
861
+ /** `GET /api/async-operations/{id}` — fetch the export operation's state. */
862
+ get(operationId: string, options?: RequestOptions): Promise<AsyncOperation>;
863
+ }
864
+ /** `client.documents` — upload a file and poll its job. */
865
+ declare class DocumentsResource {
866
+ private readonly transport;
867
+ constructor(transport: Transport);
868
+ /**
869
+ * Upload a document: presigned-url -> S3 PUT -> confirm-upload. `file` may be a
870
+ * path string, raw bytes, a `Blob`/`File`, or a `Readable`/iterable of chunks.
871
+ * With `wait: true`, polls to a terminal job status before resolving.
872
+ */
873
+ upload(file: UploadSource, options?: UploadOptions): Promise<JobHandle>;
874
+ /** `GET /api/documents/{jobId}/status` — fetch one job's current status body. */
875
+ status(jobId: string): Promise<Record<string, unknown>>;
876
+ }
877
+ /**
878
+ * Entry point. `new SpreadSpace({ apiKey: 'ss_test_...' })` or set
879
+ * `SPREADSPACE_API_KEY`.
880
+ *
881
+ * const client = new SpreadSpace({ apiKey: 'ss_test_...' });
882
+ * for await (const borrower of client.borrowers.list()) {
883
+ * // ...
884
+ * }
885
+ */
886
+ declare class SpreadSpace {
887
+ /** Borrowers: `list()`. */
888
+ readonly borrowers: BorrowersResource;
889
+ /** Loans: `list({ borrowerId?, limit? })`. */
890
+ readonly loans: LoansResource;
891
+ /** Jobs: `list()`. */
892
+ readonly jobs: JobsResource;
893
+ /** Async operations: `list({ kind?, status? })` / `get(id)` / `cancel(id)`. */
894
+ readonly asyncOperations: AsyncOperationsResource;
895
+ /** Extraction exports: `create(...)` / `get(id)`. */
896
+ readonly exports: ExportsResource;
897
+ /** Documents: `upload(file, opts)` / `status(jobId)`. */
898
+ readonly documents: DocumentsResource;
899
+ /** Webhooks: `create/list/retrieve/update/delete` endpoints + static `verifySignature`/`verifyAndParse`. */
900
+ readonly webhooks: WebhooksResource;
901
+ /** Embed: `sessions.create(...)` mints a loan-scoped `ss_embed_` token. */
902
+ readonly embed: EmbedResource;
903
+ private readonly _transport;
904
+ constructor(options?: SpreadSpaceOptions);
905
+ /** The underlying configured transport. */
906
+ get transport(): Transport;
907
+ /**
908
+ * Low-level escape hatch to any endpoint the resource helpers don't wrap.
909
+ * Returns the decoded JSON body (or `undefined` for an empty / 204 response).
910
+ */
911
+ request<T = unknown>(method: HttpMethod, path: string, options?: InternalRequestOptions): Promise<T>;
912
+ }
913
+
914
+ /**
915
+ * Exact-decimal money decoding.
916
+ *
917
+ * The SpreadSpace wire encodes money as a JSON `number`. Routed through the
918
+ * stock `JSON.parse`, every amount is forced through IEEE-754 float64 the
919
+ * instant it is read — so `0.1 + 0.2`-class drift and >2^53 truncation are
920
+ * baked in before any caller touches the value. Python (`parse_float=Decimal`)
921
+ * and C# (`System.Decimal`) already read money exactly; only TS was lossy.
922
+ *
923
+ * This module closes that gap WITHOUT touching the wire (money stays a JSON
924
+ * number outbound and inbound — non-breaking). It parses the body with
925
+ * `lossless-json`, which keeps every numeric literal as a string-backed
926
+ * {@link LosslessNumber} (no float64 round-trip), then deep-walks the decoded
927
+ * tree converting money fields to a `decimal.js` {@link Decimal} and every other
928
+ * numeric back to a plain `number`. The net effect: money is exact, everything
929
+ * else keeps its ordinary `number` type.
930
+ *
931
+ * ## Matching strategy (two tiers, one walk)
932
+ *
933
+ * - **Tier 1 — {@link SCALAR_MONEY_KEYS} (global).** The named scalar money
934
+ * fields on the typed public surface (`net_amount`, `requested_amount`, …).
935
+ * All are `decimal`/`decimal?` server-side and none collide with a non-money
936
+ * typed field, so they are converted wherever they appear. Complete + exact.
937
+ *
938
+ * - **Tier 2 — {@link PAYLOAD_MONEY_KEYS} (scoped).** The money key names that
939
+ * live ONLY inside the opaque `payload` blob (the spread / P&L / cash-flow /
940
+ * aging / bank-analysis report tree — `values`, `vals`, `total`, `net`, …).
941
+ * Several are generic words (`total`, `net`, `value`) that DO collide with
942
+ * non-money numbers elsewhere — e.g. `progress.total` is an int counter — so
943
+ * they are converted ONLY once the walk has descended through a `payload`
944
+ * key. Scoping is what makes including those generic names safe.
945
+ *
946
+ * `span` (the `BankCounterpartyTransactionDto.span` bounding box) is deliberately
947
+ * NOT a money scope: its numeric keys (`x0`, `top`, `x1`, `bottom`,
948
+ * `page_width`, `page_height`) are PDF geometry, never dollars. It carries no
949
+ * Tier-2 key names, so the default (non-payload) handling leaves it as plain
950
+ * numbers — exactly right.
951
+ *
952
+ * ## Honest coverage
953
+ *
954
+ * Tier 1 is complete and exact. Tier 2 is a heuristic keyed on field names
955
+ * inside a fully-opaque `JsonElement`: it covers the entire *current* payload
956
+ * money vocabulary, but if the server later emits a NEW money key inside the
957
+ * payload under a name not in {@link PAYLOAD_MONEY_KEYS}, that one field is
958
+ * handed back as a plain `number` (lossy) until the set is extended. The failure
959
+ * mode is "a future field stays float64", never "a non-money field becomes a
960
+ * Decimal" — non-payload numbers outside Tier 1 are never touched.
961
+ */
962
+
963
+ /**
964
+ * The decoded runtime type of a money field: an arbitrary-precision decimal.
965
+ * Matches the Python / C# semantics (`Decimal` / `System.Decimal`). Callers do
966
+ * `amount.toFixed(2)`, `amount.plus(other)`, `amount.toString()`; to send a
967
+ * value back to the API (wire stays a JSON number) use `amount.toNumber()`.
968
+ */
969
+ type Money = Decimal;
970
+ /**
971
+ * Tier 1 — scalar money field names, converted wherever they appear in a
972
+ * response. Every one is `decimal`/`decimal?` server-side and unambiguous
973
+ * across the public surface (none names a non-money typed field). Sourced from
974
+ * `api/src/Models/*.cs` (the C# declared type is ground truth — `System.Decimal`
975
+ * serializes as a JSON number, so `format: double` in OpenAPI cannot tell money
976
+ * from rate on its own).
977
+ */
978
+ declare const SCALAR_MONEY_KEYS: ReadonlySet<string>;
979
+ /**
980
+ * Tier 2 — money key names that exist ONLY inside the opaque `payload` blob
981
+ * (`LoanSpreadEnvelope.payload` — the spread / tax / P&L / cash-flow / aging /
982
+ * bank-analysis report tree). Each is `decimal` or `List<decimal>` server-side
983
+ * (`ExtractionDtos.cs`, `Spreading/PayloadBank.cs`). Converted ONLY within a
984
+ * `payload` subtree — several names (`total`, `net`, `value`) collide with
985
+ * non-money numbers at the top level, so the scope guard is load-bearing.
986
+ *
987
+ * Array elements count: a `List<decimal>` (e.g. `values`, `vals`, `total_vals`,
988
+ * a `total` row) decodes to an array whose every numeric element is money.
989
+ */
990
+ declare const PAYLOAD_MONEY_KEYS: ReadonlySet<string>;
991
+ /**
992
+ * Parse a SpreadSpace JSON response body with exact-decimal money.
993
+ *
994
+ * Drop-in for `JSON.parse(text)` on the success path: same return shape, except
995
+ * money fields are {@link Decimal} instead of `number`. Throws the same
996
+ * `SyntaxError`-family error as `JSON.parse` on malformed JSON (callers map it
997
+ * to a `NetworkError`), so error handling upstream is unchanged.
998
+ */
999
+ declare function parseBodyWithExactMoney(text: string): unknown;
1000
+
1001
+ declare const SDK_VERSION = "0.1.0";
1002
+ declare const DEFAULT_API_VERSION = "2026-05-03";
1003
+
1004
+ export { type AsyncOperation, AsyncOperationError, AsyncOperationHandle, AsyncOperationTimeout, type AsyncPager, AuthenticationError, ConflictError, type CreateEmbedIframeUrlParams, type CreateEmbedSessionParams, type CursorPaginated, DEFAULT_API_VERSION, DEFAULT_BASE_URL, type EmbedIframeUrlResult, EmbedIframeUrlsResource, EmbedResource, type EmbedSessionResult, EmbedSessionsResource, type ExtractionExportParams, type HttpMethod, type InternalRequestOptions, InvalidRequestError, JobHandle, type ListAsyncOperationsParams, type ListBorrowersParams, type ListJobsParams, type ListLoansParams, type Money, NetworkError, NotFoundError, type WaitOptions$1 as OperationWaitOptions, type OperationsTransport, PAYLOAD_MONEY_KEYS, type PaginateOptions, PermissionError, type PresignedUrl, type QueryParams, RateLimitError, type RequestOptions, SCALAR_MONEY_KEYS, SDK_VERSION, ServerError, SpreadSpace, SpreadSpaceError, type SpreadSpaceErrorParams, type SpreadSpaceOptions, TERMINAL_JOB_STATUSES, TERMINAL_STATUSES, Transport, type TransportOptions, UploadError, type UploadOptions, type UploadSource, UploadTimeout, type WaitOptions as UploadWaitOptions, type WebhookListParams, WebhooksResource, cancelOperation, classifyError, createExtractionExport, getOperation, isTerminal, paginate, parseBodyWithExactMoney, parsePresignedUrl, uploadDocument, verifyAndParseWebhook, verifyWebhook };