@astermd-hq/sdk 0.0.2

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,2825 @@
1
+ /**
2
+ * Immutable value object holding a JWT bearer token and its server-stated
3
+ * expiry.
4
+ *
5
+ * {@link TokenManager} builds one after a successful credential exchange and
6
+ * hands it to a {@link TokenStore}. Before each request the manager calls
7
+ * {@link Token.isExpired} to decide whether the cached token can be reused or a
8
+ * fresh one must be acquired.
9
+ *
10
+ * The organisation the token authorises is encoded inside the JWT itself, which
11
+ * is why no SDK method takes an organisation argument.
12
+ */
13
+ declare class Token {
14
+ #private;
15
+ /**
16
+ * @param value The raw JWT string, to be sent as `Authorization: Bearer <value>`.
17
+ * @param expiresAt Expiry as declared by the token response's
18
+ * `access_token_expiry` field. Copied, so a later mutation of
19
+ * the caller's `Date` cannot change the token.
20
+ */
21
+ constructor(value: string, expiresAt: Date);
22
+ /**
23
+ * Returns the raw JWT string.
24
+ *
25
+ * @returns The JWT, ready for use as a bearer credential.
26
+ */
27
+ value(): string;
28
+ /**
29
+ * Returns the server-stated expiry.
30
+ *
31
+ * A fresh `Date` each call, so callers cannot mutate the token's state. Note
32
+ * that the SDK treats the token as expired before this moment - see
33
+ * {@link Token.isExpired}.
34
+ *
35
+ * @returns The expiry as declared in the token response.
36
+ */
37
+ expiresAt(): Date;
38
+ /**
39
+ * Reports whether this token should be considered expired and replaced.
40
+ *
41
+ * The pre-buffer avoids a race where the token is valid when checked but
42
+ * expires in transit before the server processes the request. The default 30
43
+ * seconds means the SDK only trusts a token with at least half a minute of
44
+ * validity left. Pass a custom value only when you know something specific
45
+ * about clock skew in your environment.
46
+ *
47
+ * @param preBufferSeconds Seconds to subtract from the stated expiry when
48
+ * computing the effective cutoff.
49
+ * @returns `true` when the token is at or past its effective expiry.
50
+ */
51
+ isExpired(preBufferSeconds?: number): boolean;
52
+ }
53
+
54
+ /**
55
+ * Pluggable persistence for the bearer token.
56
+ *
57
+ * The default is {@link InMemoryTokenStore}, which caches the token for the life
58
+ * of the process. That is the right choice for a long-lived server. It is the
59
+ * wrong choice when every request runs in a fresh process or isolate - a
60
+ * serverless function, for instance - because each one would then perform its own
61
+ * credential exchange. {@link FileTokenStore} covers the single-machine case, and
62
+ * this interface covers everything else.
63
+ *
64
+ * Every method is asynchronous so a store can talk to a remote system:
65
+ *
66
+ * ```ts
67
+ * class RedisTokenStore implements TokenStore {
68
+ * constructor(private readonly redis: MyRedisClient) {}
69
+ *
70
+ * async get(): Promise<Token | null> {
71
+ * const raw = await this.redis.get('astermd:token');
72
+ * if (raw === null) return null;
73
+ * const { value, expiresAt } = JSON.parse(raw);
74
+ * return new Token(value, new Date(expiresAt));
75
+ * }
76
+ *
77
+ * async put(token: Token): Promise<void> {
78
+ * await this.redis.set(
79
+ * 'astermd:token',
80
+ * JSON.stringify({ value: token.value(), expiresAt: token.expiresAt() }),
81
+ * );
82
+ * }
83
+ *
84
+ * async clear(): Promise<void> {
85
+ * await this.redis.del('astermd:token');
86
+ * }
87
+ * }
88
+ * ```
89
+ *
90
+ * A stored token is a live credential. Whatever backs the store must be as
91
+ * protected as the client secret itself.
92
+ */
93
+ interface TokenStore {
94
+ /**
95
+ * Returns the cached token, or `null` when there is none.
96
+ *
97
+ * Expiry is not this method's concern - {@link TokenManager} checks it. A store
98
+ * should return whatever it holds and treat unreadable or corrupt state as
99
+ * absence rather than raising.
100
+ *
101
+ * @returns The stored token, or `null`.
102
+ */
103
+ get(): Promise<Token | null>;
104
+ /**
105
+ * Stores a freshly acquired token, replacing any previous one.
106
+ *
107
+ * @param token The token to persist.
108
+ */
109
+ put(token: Token): Promise<void>;
110
+ /**
111
+ * Discards the stored token so the next {@link TokenStore.get} returns `null`.
112
+ *
113
+ * Called when the server rejects the current token, before a fresh one is
114
+ * acquired.
115
+ */
116
+ clear(): Promise<void>;
117
+ }
118
+
119
+ /** Options accepted by {@link Config}. */
120
+ interface ConfigOptions {
121
+ /** OAuth2 client ID issued by AsterMD. Must be non-empty. */
122
+ clientId: string;
123
+ /** OAuth2 client secret corresponding to `clientId`. Must be non-empty. */
124
+ clientSecret: string;
125
+ /** Bare API hostname without scheme or path. Defaults to `api.astermd.com`. */
126
+ baseHost?: string;
127
+ /** Per-request timeout in seconds. Must be >= 1. Defaults to 10. */
128
+ timeoutSeconds?: number;
129
+ }
130
+ /**
131
+ * Immutable value object holding SDK configuration.
132
+ *
133
+ * Stores the OAuth2 credentials and connection settings every outbound request
134
+ * needs. It is constructed once inside {@link AsterMDClient} and never mutated.
135
+ * Validation happens eagerly in the constructor, so a misconfigured client fails
136
+ * at boot rather than at its first network call.
137
+ *
138
+ * `baseHost` is a bare hostname. The SDK always prepends `https://` and appends
139
+ * `/v1/{service}/{path}` itself - never pass a full URL.
140
+ */
141
+ declare class Config {
142
+ #private;
143
+ /**
144
+ * @param options Credentials and connection settings.
145
+ * @throws {TypeError} If `clientId` or `clientSecret` is empty, if `baseHost`
146
+ * contains a scheme or a path, or if `timeoutSeconds` is below 1.
147
+ */
148
+ constructor(options: ConfigOptions);
149
+ /**
150
+ * Returns the OAuth2 client ID used to acquire JWT bearer tokens.
151
+ *
152
+ * @returns The client ID, guaranteed non-empty.
153
+ */
154
+ clientId(): string;
155
+ /**
156
+ * Returns the OAuth2 client secret used to acquire JWT bearer tokens.
157
+ *
158
+ * @returns The client secret, guaranteed non-empty.
159
+ */
160
+ clientSecret(): string;
161
+ /**
162
+ * Returns the bare API hostname used to build request URLs.
163
+ *
164
+ * Never contains a scheme or a path. {@link UrlBuilder} prepends `https://`
165
+ * and appends `/v1/{service}/{path}` at request time.
166
+ *
167
+ * @returns The hostname, e.g. `api.astermd.com`.
168
+ */
169
+ baseHost(): string;
170
+ /**
171
+ * Returns the per-request HTTP timeout in seconds.
172
+ *
173
+ * Applied by {@link FetchHttpClient} as an `AbortSignal` deadline covering the
174
+ * whole request. A custom {@link HttpClient} may use it at its own discretion.
175
+ *
176
+ * @returns The timeout in seconds, always >= 1.
177
+ */
178
+ timeoutSeconds(): number;
179
+ /**
180
+ * Builds the fully-qualified CDN URL for a media asset path returned by the API.
181
+ *
182
+ * API responses carry relative asset paths such as
183
+ * `{org}/{channel}/products/{file}.png`. Pass one here to resolve it.
184
+ *
185
+ * Treat the result as a *fetch* URL, not a *serve* URL: download the asset
186
+ * once, store it on your own filesystem or CDN, and serve your copy to end
187
+ * users. Hot-linking the AsterMD CDN from a storefront is not supported and is
188
+ * subject to rate limiting.
189
+ *
190
+ * @param path Relative asset path as returned by the API; a leading slash is
191
+ * optional and is trimmed.
192
+ * @returns The fully-qualified URL.
193
+ */
194
+ assetUrl(path: string): string;
195
+ }
196
+
197
+ /**
198
+ * The SDK's HTTP injection seam: one method, taking a platform `Request` and
199
+ * returning a platform `Response`.
200
+ *
201
+ * The default implementation is {@link FetchHttpClient}, which wraps the global
202
+ * `fetch`. Supplying your own is how you route SDK traffic through a proxy
203
+ * agent, add your own retry policy, pin TLS settings, or stub the network out in
204
+ * tests:
205
+ *
206
+ * ```ts
207
+ * class ProxiedHttpClient implements HttpClient {
208
+ * async sendRequest(request: Request): Promise<Response> {
209
+ * return await fetch(request, { dispatcher: myProxyAgent });
210
+ * }
211
+ * }
212
+ *
213
+ * const client = new AsterMDClient({
214
+ * clientId: process.env.ASTERMD_CLIENT_ID!,
215
+ * clientSecret: process.env.ASTERMD_CLIENT_SECRET!,
216
+ * httpClient: new ProxiedHttpClient(),
217
+ * });
218
+ * ```
219
+ *
220
+ * An implementation should let a network-level failure surface as
221
+ * {@link TransportError}. {@link Transport} wraps anything else this throws
222
+ * into a {@link TransportError} as well, so every error reaching a caller of
223
+ * the SDK still extends {@link AsterMDError} - but that is a safety net, not
224
+ * licence to throw arbitrary values: prefer throwing {@link TransportError}
225
+ * directly, or an {@link AsterMDError} subclass, so the original error type is
226
+ * preserved rather than rewrapped.
227
+ *
228
+ * The debug logger is itself an implementation of this interface that decorates
229
+ * another one, which is how a single seam covers both API traffic and the
230
+ * credential exchange.
231
+ */
232
+ interface HttpClient {
233
+ /**
234
+ * Sends one request and resolves with the response.
235
+ *
236
+ * @param request The fully-built request, including URL, method, headers, and
237
+ * body. Implementations must not mutate it.
238
+ * @returns The response as received. A non-2xx status is a normal return, not
239
+ * an error - {@link Transport} maps statuses to errors.
240
+ * @throws {TransportError} On a network-level failure, where no response
241
+ * arrived.
242
+ */
243
+ sendRequest(request: Request): Promise<Response>;
244
+ }
245
+
246
+ /**
247
+ * Destination for debug log entries.
248
+ *
249
+ * Called once per request with a fully formatted entry - timestamp, curl
250
+ * command, and response. Return a promise to have the SDK await the write. Any
251
+ * error it throws is swallowed: a failing log must never fail an API call.
252
+ */
253
+ type DebugSink = (entry: string) => void | Promise<void>;
254
+
255
+ /** Options accepted by {@link Response}. */
256
+ interface ResponseOptions<T> {
257
+ /** HTTP status code of the response (200–299). */
258
+ statusCode: number;
259
+ /** Contents of the envelope's `data` key. */
260
+ data: T;
261
+ /** Contents of the envelope's `meta` key; may be empty. */
262
+ meta: Record<string, unknown>;
263
+ /** Human-readable `message` string from the envelope. */
264
+ message: string;
265
+ /** Raw response body, before decoding. */
266
+ raw: string;
267
+ }
268
+ /**
269
+ * Value object representing a successful API response with its envelope
270
+ * already decoded.
271
+ *
272
+ * Every AsterMD response that returns HTTP 2xx is wrapped in a standard JSON
273
+ * envelope:
274
+ *
275
+ * ```json
276
+ * { "success": true, "message": "Record created.", "data": { }, "meta": { } }
277
+ * ```
278
+ *
279
+ * {@link Transport} decodes that envelope and builds a `Response` from it.
280
+ * `data()` holds the primary resource payload - `{ session: "<uuid>" }` for
281
+ * session creation, for instance - while `meta()` carries pagination cursors and
282
+ * other out-of-band information where the endpoint emits them.
283
+ *
284
+ * Non-2xx responses are never wrapped in this object; they raise
285
+ * {@link ApiError} or one of its subclasses instead.
286
+ *
287
+ * The shape of `data()` varies by endpoint, so it defaults to an untyped record.
288
+ * A caller who knows the shape may narrow it:
289
+ *
290
+ * ```ts
291
+ * const patient = await client.patients().view<Patient>('p-1');
292
+ * ```
293
+ *
294
+ * The instance and its top-level `data`/`meta` objects are frozen, which guards
295
+ * against accidental reassignment or a stray `delete` on this response. That
296
+ * freeze is shallow, though: an object or array nested inside `data()` is the
297
+ * server's payload passed through unchanged, and remains as mutable as any
298
+ * other object you receive from `JSON.parse`. Treat nested values as you would
299
+ * any other deserialised response - copy before mutating if that matters to
300
+ * you - rather than relying on this class to protect them.
301
+ */
302
+ declare class Response$1<T = Record<string, unknown>> {
303
+ #private;
304
+ /**
305
+ * @param options The decoded envelope fields.
306
+ */
307
+ constructor(options: ResponseOptions<T>);
308
+ /**
309
+ * Returns the HTTP status code, always 200–299 for a `Response` instance.
310
+ *
311
+ * @returns The status code, e.g. `200` or `201`.
312
+ */
313
+ statusCode(): number;
314
+ /**
315
+ * Returns the primary resource payload from the envelope's `data` key.
316
+ *
317
+ * The shape varies by endpoint - see the TSDoc on the resource method you
318
+ * called. Empty when the server returned no data.
319
+ *
320
+ * @returns The decoded `data` object.
321
+ */
322
+ data(): T;
323
+ /**
324
+ * Returns the envelope's `meta` key, typically pagination information.
325
+ *
326
+ * Present on paginated list endpoints such as `products/list` and
327
+ * `treatments/list`, where it may carry `total`, `per_page`, `current_page`,
328
+ * and `last_page`. Empty for endpoints that emit no `meta` block.
329
+ *
330
+ * @returns The decoded `meta` object.
331
+ */
332
+ meta(): Readonly<Record<string, unknown>>;
333
+ /**
334
+ * Returns the human-readable `message` string from the envelope.
335
+ *
336
+ * Usually a short confirmation such as `"Session created."`. Empty when the
337
+ * server omitted the field.
338
+ *
339
+ * @returns The envelope message, possibly empty.
340
+ */
341
+ message(): string;
342
+ /**
343
+ * Returns the raw response body exactly as received, before decoding.
344
+ *
345
+ * Useful for low-level debugging. Do not log this value for endpoints carrying
346
+ * PHI, such as `patients/health-information`.
347
+ *
348
+ * @returns The raw JSON body.
349
+ */
350
+ raw(): string;
351
+ }
352
+
353
+ /** Overrides accepted by {@link FileUpload.fromPath}. */
354
+ interface FileUploadFromPathOptions {
355
+ /** Name to send in the part header. Defaults to the path's base name. */
356
+ fileName?: string;
357
+ /** MIME type to send. Defaults to one derived from the extension. */
358
+ mimeType?: string;
359
+ }
360
+ /** Overrides accepted by {@link FileUpload.fromContents}. */
361
+ interface FileUploadFromContentsOptions {
362
+ /** MIME type to send. Defaults to one derived from the file name. */
363
+ mimeType?: string;
364
+ }
365
+ /**
366
+ * An immutable file, in memory, ready to be sent as a `multipart/form-data`
367
+ * part.
368
+ *
369
+ * A handful of AsterMD endpoints take bytes rather than JSON - the
370
+ * intake-submission file endpoints on the `sales` service are the current
371
+ * example. Those bytes come from very different places: a file your users just
372
+ * uploaded and that now sits on local disk, or a slice of a large file being fed
373
+ * through the multipart flow one chunk at a time. This class is the single shape
374
+ * resource methods accept for both, so {@link Transport} has exactly one thing
375
+ * to encode.
376
+ *
377
+ * Build it through one of the two factories - the constructor is private, so an
378
+ * instance always carries a file name and a MIME type:
379
+ *
380
+ * ```ts
381
+ * // From a file on disk; name and MIME type come from the path.
382
+ * await client.intakeSubmissions().uploadFile(
383
+ * sessionId,
384
+ * await FileUpload.fromPath('/var/uploads/id-front.jpg'),
385
+ * );
386
+ *
387
+ * // From bytes already in memory: one 10 MB slice of a large video.
388
+ * await client.intakeSubmissions().uploadMultipartPart({
389
+ * uploadId,
390
+ * partNumber: 1,
391
+ * part: FileUpload.fromContents(chunk, 'consult-video.mp4'),
392
+ * });
393
+ * ```
394
+ *
395
+ * The whole file is held in memory, so it is deliberately not the right tool for
396
+ * a 200 MB video - use {@link IntakeSubmissions.uploadLargeFile}, which streams
397
+ * from disk and only ever materialises one part at a time.
398
+ *
399
+ * File names are normalised on construction: directory components are dropped
400
+ * and quote, backslash, and newline characters removed, so a caller-supplied
401
+ * name can never break out of the header it is written into.
402
+ */
403
+ declare class FileUpload {
404
+ #private;
405
+ private constructor();
406
+ /**
407
+ * Reads a file from local disk into an upload.
408
+ *
409
+ * The file is read in full and held in memory, so keep this for the sizes the
410
+ * single-shot endpoints allow. Without `options.fileName` the path's base name
411
+ * is used, and without `options.mimeType` the type is derived from the
412
+ * extension, falling back to `application/octet-stream`.
413
+ *
414
+ * @param path Path to an existing, readable file.
415
+ * @param options Optional name and MIME type overrides.
416
+ * @returns An upload carrying the file's bytes, name, and MIME type.
417
+ * @throws {TypeError} If `path` is not a readable file, or if the resulting
418
+ * file name normalises to nothing usable.
419
+ */
420
+ static fromPath(path: string, options?: FileUploadFromPathOptions): Promise<FileUpload>;
421
+ /**
422
+ * Wraps bytes already held in memory into an upload.
423
+ *
424
+ * Use this when the bytes never came from a file on disk: a slice of a larger
425
+ * file being sent through the multipart flow, a payload received over the
426
+ * wire, or content generated at runtime. `fileName` is required because there
427
+ * is no path to derive one from.
428
+ *
429
+ * @param contents The raw bytes to send as the part body.
430
+ * @param fileName Name to send in the part header.
431
+ * @param options Optional MIME type override.
432
+ * @returns An upload carrying the given bytes, name, and MIME type.
433
+ * @throws {TypeError} If `fileName` normalises to nothing usable.
434
+ */
435
+ static fromContents(contents: Uint8Array, fileName: string, options?: FileUploadFromContentsOptions): FileUpload;
436
+ /**
437
+ * Returns the raw bytes that will form the body of the multipart part.
438
+ *
439
+ * @returns The file's contents.
440
+ */
441
+ contents(): Uint8Array;
442
+ /**
443
+ * Returns the normalised file name sent in the part header.
444
+ *
445
+ * The server uses this to derive the stored object name, inserting a unique
446
+ * identifier before the extension so repeat uploads of the same name never
447
+ * collide.
448
+ *
449
+ * @returns The file name, stripped of directory components and
450
+ * header-breaking characters.
451
+ */
452
+ fileName(): string;
453
+ /**
454
+ * Returns the MIME type sent for the part.
455
+ *
456
+ * @returns The MIME type, e.g. `image/jpeg`.
457
+ */
458
+ mimeType(): string;
459
+ /**
460
+ * Returns the size of the upload in bytes.
461
+ *
462
+ * Useful for checking a file against an endpoint's documented limit before
463
+ * spending a round trip on it, and for the `fileSize` value the multipart
464
+ * initiate call requires.
465
+ *
466
+ * @returns The number of bytes in {@link FileUpload.contents}.
467
+ */
468
+ size(): number;
469
+ }
470
+
471
+ /** Query string parameters accepted by {@link UrlBuilder.build}. */
472
+ type QueryParams = Record<string, string | number | boolean>;
473
+ /**
474
+ * Builds fully-qualified HTTPS request URLs following the `/v1/{service}/{path}`
475
+ * convention.
476
+ *
477
+ * Every AsterMD endpoint lives under `https://{host}/v1/{service}/{path}`, where
478
+ * `{service}` is `auth`, `sales`, or `platform`. This class is the one place that
479
+ * convention is encoded, so resources never construct URLs and the SDK can never
480
+ * be handed a full URL by a consumer.
481
+ *
482
+ * Path placeholders such as `{id}` are substituted and percent-encoded; query
483
+ * parameters are appended with PHP-compatible encoding so a URL built here is
484
+ * byte-identical to the one the PHP SDK would build for the same call.
485
+ */
486
+ declare class UrlBuilder {
487
+ #private;
488
+ /**
489
+ * @param baseHost Bare hostname to prepend, e.g. `api.astermd.com`.
490
+ */
491
+ constructor(baseHost: string);
492
+ /**
493
+ * Constructs the fully-qualified URL for a service and path.
494
+ *
495
+ * Placeholder tokens in `path` - `{id}`, `{session}`, `{form_json_identifier}`
496
+ * - are replaced with the matching values from `pathParams`, percent-encoded.
497
+ * A placeholder with no matching entry is left untouched rather than guessed
498
+ * at, which surfaces the programming error in the request URL. Non-empty
499
+ * `query` entries are serialised and appended after `?`.
500
+ *
501
+ * @param service Service segment, e.g. `sales`, `auth`, `platform`.
502
+ * @param path Path relative to `/v1/{service}`, with optional `{placeholder}`
503
+ * tokens.
504
+ * @param pathParams Placeholder name → replacement value.
505
+ * @param query Query string parameters.
506
+ * @returns The fully-qualified URL.
507
+ */
508
+ build(service: string, path: string, pathParams?: Record<string, string | number>, query?: QueryParams): string;
509
+ }
510
+
511
+ /** One outbound API call, described declaratively. */
512
+ interface SendOptions {
513
+ /** Service segment of the URL: `sales`, `auth`, or `platform`. */
514
+ service: string;
515
+ /** HTTP method in uppercase. */
516
+ method: string;
517
+ /** Path relative to `/v1/{service}`, with `{placeholder}` tokens. */
518
+ path: string;
519
+ /** Placeholder name → value, substituted into `path`. */
520
+ pathParams?: Record<string, string | number>;
521
+ /** Query string parameters. */
522
+ query?: QueryParams;
523
+ /**
524
+ * Request body, serialised as JSON. An empty object is sent as `{}`, which
525
+ * every AsterMD endpoint requires of an object-shaped body. Omit for no body.
526
+ * Mutually exclusive with `file`.
527
+ */
528
+ body?: Record<string, unknown> | null;
529
+ /** Additional headers, e.g. `x-phi-verification-token`. */
530
+ headers?: Record<string, string>;
531
+ /**
532
+ * File to send as a `multipart/form-data` body under the field name `file`.
533
+ * Mutually exclusive with `body`.
534
+ */
535
+ file?: FileUpload | null;
536
+ }
537
+ /** Collaborators {@link Transport} needs. */
538
+ interface TransportOptions {
539
+ /** The client all outbound calls go through. */
540
+ httpClient: HttpClient;
541
+ /** Builds request URLs. */
542
+ urlBuilder: UrlBuilder;
543
+ /**
544
+ * Returns the current bearer token, or `null` for an unauthenticated request.
545
+ * {@link TokenManager} passes a provider that always returns `null` for its own
546
+ * credential exchange, which is what stops that call recursing.
547
+ */
548
+ tokenProvider: () => Promise<string | null>;
549
+ /**
550
+ * Invoked at most once per `send()` when the server answers 401. Should
551
+ * refresh the token and resolve `true` to request a replay, or `false` to let
552
+ * the 401 surface.
553
+ */
554
+ onUnauthorized: () => Promise<boolean>;
555
+ }
556
+ /**
557
+ * The single HTTP chokepoint for every outbound API call.
558
+ *
559
+ * Each resource method delegates to {@link Transport.send}, which owns the whole
560
+ * request lifecycle: URL construction, bearer-token attachment, request
561
+ * building, body serialisation, dispatch through the {@link HttpClient} seam,
562
+ * envelope decoding, and the mapping from HTTP status to error class. Resources
563
+ * never see a `Request`, a `Response`, or a line of JSON - that separation is
564
+ * what keeps seventeen resource classes trivial and testable.
565
+ *
566
+ * The 401-retry protocol is built in. On a 401 the transport calls
567
+ * `onUnauthorized` once, which in the production wiring refreshes the token, and
568
+ * replays the request if that resolves `true`. A second 401 raises
569
+ * {@link AuthenticationError} with no further retry.
570
+ *
571
+ * Status mapping:
572
+ *
573
+ * - 200–299 → {@link Response} with the envelope decoded
574
+ * - 401 → {@link AuthenticationError}
575
+ * - 404 → {@link NotFoundError}
576
+ * - 422 → {@link ValidationError}, with field errors extracted from `errors`
577
+ * - 429 → {@link RateLimitError}, with `Retry-After` parsed when present
578
+ * - anything else → {@link ApiError}
579
+ * - no response at all → {@link TransportError}, from the {@link HttpClient}
580
+ */
581
+ declare class Transport {
582
+ #private;
583
+ /**
584
+ * @param options The collaborators to use for every call.
585
+ */
586
+ constructor(options: TransportOptions);
587
+ /**
588
+ * Dispatches one API call and returns the decoded success envelope.
589
+ *
590
+ * On a 401 the token is refreshed and the call is replayed once before
591
+ * {@link AuthenticationError} is raised.
592
+ *
593
+ * @param options The call to make.
594
+ * @returns The decoded envelope; `data()` holds the resource payload.
595
+ * @throws {TypeError} If both `body` and `file` are supplied.
596
+ * @throws {AuthenticationError} On a 401 that a token refresh did not recover.
597
+ * @throws {NotFoundError} On a 404.
598
+ * @throws {ValidationError} On a 422; inspect `fieldErrors()`.
599
+ * @throws {RateLimitError} On a 429; inspect `retryAfter()`.
600
+ * @throws {ApiError} On any other non-2xx status.
601
+ * @throws {TransportError} If the request never completed.
602
+ */
603
+ send<T = Record<string, unknown>>(options: SendOptions): Promise<Response$1<T>>;
604
+ }
605
+
606
+ /**
607
+ * Base class for every SDK resource; holds the shared {@link Transport}.
608
+ *
609
+ * Resource classes extend this and call `this.transport.send()` to dispatch every
610
+ * outbound request. A resource never builds a request, touches a header, or
611
+ * decodes JSON - all of that belongs to `Transport` alone. That is what keeps
612
+ * seventeen resource classes small enough to read at a glance and testable
613
+ * without a network.
614
+ */
615
+ declare abstract class AbstractResource {
616
+ /** The shared transport used for all outbound API calls. */
617
+ protected readonly transport: Transport;
618
+ /**
619
+ * @param transport The transport every call from this resource goes through.
620
+ */
621
+ protected constructor(transport: Transport);
622
+ }
623
+
624
+ /** Options for {@link Carts.create} and {@link Carts.update}. */
625
+ interface CartWriteOptions {
626
+ /** The session the cart belongs to. */
627
+ session: string;
628
+ /**
629
+ * The cart's full contents. Each entry identifies a product and quantity, and
630
+ * a variant where the product has them. This is the complete list, not a
631
+ * delta - see the API reference in your AsterMD dashboard for the fields.
632
+ */
633
+ items: Record<string, unknown>[];
634
+ }
635
+ /**
636
+ * Carts - what the visitor intends to buy, recorded against their session.
637
+ *
638
+ * The cart is a server-side record of intent, kept so abandonment can be measured
639
+ * and so the eventual order can be reconciled against what was selected. It is
640
+ * not a pricing engine and not a checkout: money is handled by your own payment
641
+ * flow, and the resulting order arrives through
642
+ * {@link Treatments.create} or {@link Treatments.sync}.
643
+ *
644
+ * Both methods take the cart's *whole* contents rather than a delta. Mirror your
645
+ * storefront's cart state into `update()` whenever it changes, and the
646
+ * server-side record stays in step with what the visitor is actually looking at.
647
+ */
648
+ declare class Carts extends AbstractResource {
649
+ /**
650
+ * @param transport The shared transport.
651
+ */
652
+ constructor(transport: Transport);
653
+ /**
654
+ * Creates the cart for a session.
655
+ *
656
+ * Call this the first time the visitor puts something in their cart. The
657
+ * session must already exist. If a cart is already recorded for the session,
658
+ * use {@link Carts.update} instead.
659
+ *
660
+ * @param options The session and the cart's contents.
661
+ * @returns The created cart.
662
+ * @throws {ValidationError} If the session or an item is rejected.
663
+ * @throws {ApiError} On any other non-2xx status.
664
+ * @throws {TransportError} If the request never completed.
665
+ */
666
+ create<T = Record<string, unknown>>(options: CartWriteOptions): Promise<Response$1<T>>;
667
+ /**
668
+ * Replaces the contents of a session's cart.
669
+ *
670
+ * The item list is authoritative: whatever you send becomes the cart. Send the
671
+ * full list on every change rather than only what moved, and pass an empty list
672
+ * to record that the visitor emptied their cart.
673
+ *
674
+ * @param options The session and the cart's new contents.
675
+ * @returns The updated cart.
676
+ * @throws {NotFoundError} If no cart exists for the session.
677
+ * @throws {ValidationError} If an item is rejected.
678
+ * @throws {ApiError} On any other non-2xx status.
679
+ * @throws {TransportError} If the request never completed.
680
+ */
681
+ update<T = Record<string, unknown>>(options: CartWriteOptions): Promise<Response$1<T>>;
682
+ }
683
+
684
+ /**
685
+ * Categories - how the catalogue is grouped for browsing.
686
+ *
687
+ * A category is a merchandising grouping a storefront renders as navigation: a
688
+ * condition treated, a product family, a landing-page collection. Products
689
+ * reference the categories they belong to, so fetching the category list is
690
+ * usually the first call a catalogue page makes.
691
+ *
692
+ * Read-only, and configured in your AsterMD dashboard.
693
+ */
694
+ declare class Categories extends AbstractResource {
695
+ /**
696
+ * @param transport The shared transport.
697
+ */
698
+ constructor(transport: Transport);
699
+ /**
700
+ * Lists categories, filtered and paginated.
701
+ *
702
+ * Pagination details come back in `meta()`. The accepted filters are in the
703
+ * API reference in your AsterMD dashboard.
704
+ *
705
+ * @param query Filters and pagination parameters.
706
+ * @returns The matching categories, with pagination in `meta()`.
707
+ * @throws {ValidationError} If a filter is rejected.
708
+ * @throws {ApiError} On any other non-2xx status.
709
+ * @throws {TransportError} If the request never completed.
710
+ */
711
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
712
+ /**
713
+ * Fetches one category.
714
+ *
715
+ * @param id The category's id.
716
+ * @returns The category record.
717
+ * @throws {NotFoundError} If the category does not exist or is out of scope.
718
+ * @throws {ApiError} On any other non-2xx status.
719
+ * @throws {TransportError} If the request never completed.
720
+ */
721
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
722
+ }
723
+
724
+ /**
725
+ * Channels - the storefront's own configuration, as the server holds it.
726
+ *
727
+ * A channel is one selling surface: a brand, a site, a campaign landing page.
728
+ * Which products it may sell, which payment processor it uses, and which
729
+ * questionnaires those products require are all channel-level configuration set
730
+ * in your AsterMD dashboard. Your storefront reads that configuration rather than
731
+ * duplicating it, so changing what a brand sells does not need a deployment.
732
+ *
733
+ * Three views, in increasing depth. {@link Channels.view} returns the channel
734
+ * itself. {@link Channels.assignedProducts} returns what it may sell.
735
+ * {@link Channels.details} returns the whole tree - channel, payment processor,
736
+ * products, variants, prices, and per-integration mappings - in a single call,
737
+ * which is what a storefront usually wants at boot.
738
+ *
739
+ * Channel configuration changes rarely. Fetch it rather than embedding a copy,
740
+ * but caching the result for minutes is reasonable.
741
+ */
742
+ declare class Channels extends AbstractResource {
743
+ /**
744
+ * @param transport The shared transport.
745
+ */
746
+ constructor(transport: Transport);
747
+ /**
748
+ * Fetches a channel's own configuration.
749
+ *
750
+ * The channel record itself - name, type, status - without its product
751
+ * catalogue.
752
+ *
753
+ * @param id The channel's id.
754
+ * @returns The channel record.
755
+ * @throws {NotFoundError} If the channel does not exist or is out of scope.
756
+ * @throws {ApiError} On any other non-2xx status.
757
+ * @throws {TransportError} If the request never completed.
758
+ */
759
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
760
+ /**
761
+ * Fetches the products assigned to a channel.
762
+ *
763
+ * What this selling surface is permitted to sell. Use it to build a catalogue
764
+ * page without pulling the full detail tree.
765
+ *
766
+ * @param id The channel's id.
767
+ * @returns The assigned products.
768
+ * @throws {NotFoundError} If the channel does not exist.
769
+ * @throws {ApiError} On any other non-2xx status.
770
+ * @throws {TransportError} If the request never completed.
771
+ */
772
+ assignedProducts<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
773
+ /**
774
+ * Fetches a channel's complete configuration tree.
775
+ *
776
+ * Channel, payment processor, products, variants, prices, and per-integration
777
+ * mappings in one response - everything a storefront needs to render its
778
+ * catalogue without further calls. The envelope is deeply nested and carries
779
+ * server-side field names; see the API reference in your AsterMD dashboard for
780
+ * its shape. Pass `data()` through {@link ChannelDetail.from} to flatten it.
781
+ *
782
+ * @param id The channel's id.
783
+ * @returns The full configuration tree.
784
+ * @throws {NotFoundError} If the channel does not exist.
785
+ * @throws {ApiError} On any other non-2xx status.
786
+ * @throws {TransportError} If the request never completed.
787
+ */
788
+ details<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
789
+ }
790
+
791
+ /**
792
+ * Checkout-funnel events recorded against a session.
793
+ *
794
+ * {@link CheckoutEvents.create} always records `CheckoutVisited` - it is the
795
+ * call that opens the funnel record. Every later state is reported through
796
+ * {@link CheckoutEvents.update} with one of the remaining values, which lets the
797
+ * server reconstruct where a prospect abandoned checkout and which upsells they
798
+ * were shown.
799
+ */
800
+ declare const CheckoutEvent: Readonly<{
801
+ /** The prospect reached the checkout page. Recorded implicitly by `create()`. */
802
+ readonly CheckoutVisited: "checkout_visited";
803
+ /** An upsell was presented to the prospect. */
804
+ readonly UpsellOffered: "upsell_offered";
805
+ /** The prospect accepted a presented upsell. */
806
+ readonly UpsellAccepted: "upsell_accepted";
807
+ /** The prospect declined a presented upsell. */
808
+ readonly UpsellDeclined: "upsell_declined";
809
+ /** The order was placed successfully. */
810
+ readonly OrderPlaced: "order_placed";
811
+ /** The order was declined, typically by the payment processor. */
812
+ readonly OrderDeclined: "order_declined";
813
+ }>;
814
+ /** Any checkout-funnel event wire value. A bare string literal satisfies this type. */
815
+ type CheckoutEvent = (typeof CheckoutEvent)[keyof typeof CheckoutEvent];
816
+
817
+ /** Options for {@link CheckoutEvents.update}. */
818
+ interface CheckoutEventUpdateOptions {
819
+ /** The session whose checkout record is being advanced. */
820
+ session: string;
821
+ /** The funnel state now reached. */
822
+ event: CheckoutEvent;
823
+ /**
824
+ * Additional context for this event - which upsell was shown, the order total,
825
+ * a decline reason. Forwarded unmodified except that it can never override
826
+ * `event`.
827
+ */
828
+ data?: Record<string, unknown>;
829
+ }
830
+ /**
831
+ * Checkout events - the visitor's progress through the checkout funnel.
832
+ *
833
+ * Where {@link Sessions} records that a visit happened and {@link Carts} records
834
+ * what was selected, this resource records what happened at the till: the
835
+ * checkout page opening, each upsell offered and accepted or declined, and
836
+ * finally whether the order was placed or declined. Together those events are
837
+ * what makes checkout abandonment measurable.
838
+ *
839
+ * {@link CheckoutEvents.create} opens the record and always registers
840
+ * `checkout_visited`; every later state goes through
841
+ * {@link CheckoutEvents.update} with the matching {@link CheckoutEvent}. Neither
842
+ * call can have its event overridden by the extra payload, so the funnel state
843
+ * the server records is always the one the SDK's caller chose explicitly.
844
+ */
845
+ declare class CheckoutEvents extends AbstractResource {
846
+ /**
847
+ * @param transport The shared transport.
848
+ */
849
+ constructor(transport: Transport);
850
+ /**
851
+ * Opens the checkout record for a session, registering `checkout_visited`.
852
+ *
853
+ * Call this once, when the visitor reaches the checkout page. The event is
854
+ * fixed - this is the call that starts the funnel record - and `currency`
855
+ * defaults to `USD` when you do not supply one, since the server requires a
856
+ * currency to interpret any amounts in the payload.
857
+ *
858
+ * @param session The session reaching checkout.
859
+ * @param data Additional context, e.g. `cart_total`, `items_count`,
860
+ * `currency`. Cannot override `session` or `event`.
861
+ * @returns The created checkout-event record.
862
+ * @throws {ValidationError} If the session or payload is rejected.
863
+ * @throws {ApiError} On any other non-2xx status.
864
+ * @throws {TransportError} If the request never completed.
865
+ */
866
+ create<T = Record<string, unknown>>(session: string, data?: Record<string, unknown>): Promise<Response$1<T>>;
867
+ /**
868
+ * Advances a session's checkout record to a new funnel state.
869
+ *
870
+ * Send one call per state the visitor reaches: an upsell offered, then accepted
871
+ * or declined, then the order placed or declined. The payload carries whatever
872
+ * context that state needs, but it can never override `event` - the state
873
+ * recorded is always the one you passed explicitly.
874
+ *
875
+ * @param options The session, the new state, and any context for it.
876
+ * @returns The updated checkout-event record.
877
+ * @throws {NotFoundError} If no checkout record exists for the session.
878
+ * @throws {ValidationError} If the event or payload is rejected.
879
+ * @throws {ApiError} On any other non-2xx status.
880
+ * @throws {TransportError} If the request never completed.
881
+ */
882
+ update<T = Record<string, unknown>>(options: CheckoutEventUpdateOptions): Promise<Response$1<T>>;
883
+ }
884
+
885
+ /**
886
+ * Doctors' networks - handing a case to the clinicians who will review it.
887
+ *
888
+ * A doctors' network is the panel of licensed clinicians configured for your
889
+ * organisation. Syncing pushes a case to that panel so a clinician can pick it
890
+ * up: it is the bridge between the commercial half of the order flow and the
891
+ * clinical one.
892
+ *
893
+ * The single method here is unusual in the SDK for validating before it sends. A
894
+ * sync with neither an opportunity to attach to nor enough detail to identify the
895
+ * person would be accepted by the transport and rejected by the server, having
896
+ * cost a round trip to learn something the SDK can see locally. So it checks
897
+ * first, and raises `TypeError` rather than making the call.
898
+ *
899
+ * ```ts
900
+ * await client.doctorsNetworks().sync({ opportunity_id: opportunityId });
901
+ *
902
+ * // or, without an opportunity yet:
903
+ * await client.doctorsNetworks().sync({
904
+ * user_info: { first_name: 'Jane', email: 'jane@example.test' },
905
+ * });
906
+ * ```
907
+ */
908
+ declare class DoctorsNetworks extends AbstractResource {
909
+ /**
910
+ * @param transport The shared transport.
911
+ */
912
+ constructor(transport: Transport);
913
+ /**
914
+ * Pushes a case to the configured doctors' network.
915
+ *
916
+ * The payload must identify who the case is about, one of two ways: an
917
+ * `opportunity_id` for a prospect already recorded, or a `user_info` object
918
+ * carrying at least `first_name` and `email` for one who is not. Everything
919
+ * else is forwarded unmodified.
920
+ *
921
+ * An `emit_opportunity` key is removed before sending - that flag is the
922
+ * server's to set, not a caller's. Your object is not modified; the SDK copies
923
+ * it first.
924
+ *
925
+ * @param payload The case to push. Requires either a non-empty
926
+ * `opportunity_id` string, or a `user_info` object with non-empty
927
+ * `first_name` and `email` strings.
928
+ * @returns The sync result.
929
+ * @throws {TypeError} If the payload identifies neither an opportunity nor a
930
+ * person.
931
+ * @throws {ValidationError} If the server rejects a field.
932
+ * @throws {NotFoundError} If the referenced opportunity does not exist.
933
+ * @throws {ApiError} On any other non-2xx status.
934
+ * @throws {TransportError} If the request never completed.
935
+ */
936
+ sync<T = Record<string, unknown>>(payload: Record<string, unknown>): Promise<Response$1<T>>;
937
+ }
938
+
939
+ /**
940
+ * Geo - where a visitor is, and whether you are allowed to serve them.
941
+ *
942
+ * Telehealth is licensed jurisdiction by jurisdiction, so location is not a
943
+ * personalisation detail here - it decides whether an order flow may start at
944
+ * all. These two endpoints answer the two different questions that follow from
945
+ * that.
946
+ *
947
+ * {@link Geo.info} resolves an IP address to a location, which is what you want
948
+ * for pre-filling a state selector or routing a visitor to the right regional
949
+ * content. {@link Geo.blocklist} answers the narrower question of whether this
950
+ * address is barred - a jurisdiction you are not licensed in, a known proxy or
951
+ * anonymiser. Check the blocklist at the top of the funnel: turning a visitor
952
+ * away on the landing page is far cheaper than doing it after they have completed
953
+ * an intake questionnaire.
954
+ *
955
+ * Pass the *visitor's* address, not your server's. Behind a proxy or load
956
+ * balancer that means reading the forwarded-for header your infrastructure sets,
957
+ * with the same care you would apply anywhere else - that header is
958
+ * caller-supplied unless your edge overwrites it.
959
+ *
960
+ * Both take their input as a query parameter, and redaction never covers the URL,
961
+ * so a debug entry for these calls contains the IP address that was checked.
962
+ */
963
+ declare class Geo extends AbstractResource {
964
+ /**
965
+ * @param transport The shared transport.
966
+ */
967
+ constructor(transport: Transport);
968
+ /**
969
+ * Resolves an IP address to a geographic location.
970
+ *
971
+ * Returns the configured provider's answer, normalised. Treat it as a strong
972
+ * hint rather than proof - a VPN or a mobile carrier's routing can place a
973
+ * visitor a long way from where they are. For a licensing decision, prefer
974
+ * {@link Geo.blocklist} and the state the visitor declares.
975
+ *
976
+ * @param ip The visitor's IP address, v4 or v6.
977
+ * @returns The resolved location.
978
+ * @throws {ValidationError} If the address is malformed.
979
+ * @throws {ApiError} On any other non-2xx status.
980
+ * @throws {TransportError} If the request never completed.
981
+ */
982
+ info<T = Record<string, unknown>>(ip: string): Promise<Response$1<T>>;
983
+ /**
984
+ * Reports whether an IP address is barred from starting an order flow.
985
+ *
986
+ * Covers jurisdictions your organisation is not licensed for, along with known
987
+ * proxies and anonymisers. Call it as early as the landing page: refusing a
988
+ * visitor before they invest time in a questionnaire is better for them and
989
+ * cheaper for you.
990
+ *
991
+ * @param ip The visitor's IP address, v4 or v6.
992
+ * @returns The blocklist verdict.
993
+ * @throws {ValidationError} If the address is malformed.
994
+ * @throws {ApiError} On any other non-2xx status.
995
+ * @throws {TransportError} If the request never completed.
996
+ */
997
+ blocklist<T = Record<string, unknown>>(ip: string): Promise<Response$1<T>>;
998
+ }
999
+
1000
+ /**
1001
+ * Journey events recorded alongside intake-submission records.
1002
+ *
1003
+ * Every call to {@link IntakeSubmissions.create} or
1004
+ * {@link IntakeSubmissions.update} must carry one of these events to advance the
1005
+ * server-side order-flow state machine.
1006
+ *
1007
+ * Two separate flows exist, each with three events.
1008
+ *
1009
+ * Pre-qualifying flow - emitted while the prospect answers eligibility
1010
+ * questions:
1011
+ * - `PreQualifyingInitiated` → first form step (use with `create()`)
1012
+ * - `PreQualifyingInProgress` → intermediate steps (use with `update()`)
1013
+ * - `PreQualifyingCompleted` → final step / form submitted (use with `update()`)
1014
+ *
1015
+ * Intake flow - emitted while the prospect fills in the full intake
1016
+ * questionnaire:
1017
+ * - `IntakeInitiated` → first form step (use with `create()`)
1018
+ * - `IntakeInProgress` → intermediate steps (use with `update()`)
1019
+ * - `IntakeCompleted` → final step / form submitted (use with `update()`)
1020
+ *
1021
+ * In both flows the `*Initiated` event always goes to `create()`, the call that
1022
+ * creates the server-side record, and the `*InProgress` / `*Completed` events go
1023
+ * to subsequent `update()` calls.
1024
+ */
1025
+ declare const Event: Readonly<{
1026
+ /** The prospect started the pre-qualifying questionnaire. Pass to `create()`. */
1027
+ readonly PreQualifyingInitiated: "pre_qualifying_initiated";
1028
+ /** The prospect advanced through an intermediate pre-qualifying step. Pass to `update()`. */
1029
+ readonly PreQualifyingInProgress: "pre_qualifying_inprogress";
1030
+ /** The prospect submitted the pre-qualifying form. Pass to `update()`. */
1031
+ readonly PreQualifyingCompleted: "pre_qualifying_completed";
1032
+ /** The prospect started the intake questionnaire. Pass to `create()`. */
1033
+ readonly IntakeInitiated: "intake_initiated";
1034
+ /** The prospect advanced through an intermediate intake step. Pass to `update()`. */
1035
+ readonly IntakeInProgress: "intake_inprogress";
1036
+ /** The prospect submitted the intake form. Pass to `update()`. */
1037
+ readonly IntakeCompleted: "intake_completed";
1038
+ }>;
1039
+ /** Any journey event wire value. A bare string literal satisfies this type. */
1040
+ type Event = (typeof Event)[keyof typeof Event];
1041
+
1042
+ /** Options for {@link IntakeSubmissions.view}. */
1043
+ interface IntakeViewOptions {
1044
+ /** The intake submission's id. */
1045
+ id: string;
1046
+ /** The teleform the submission answers. */
1047
+ teleformId: string;
1048
+ }
1049
+ /** Options for {@link IntakeSubmissions.create} and {@link IntakeSubmissions.update}. */
1050
+ interface IntakeWriteOptions {
1051
+ /** The session the submission belongs to. */
1052
+ session: string;
1053
+ /** The journey event this call records. */
1054
+ event: Event;
1055
+ /** The teleform being answered. */
1056
+ teleformId: string;
1057
+ /**
1058
+ * The answers, one entry per field: `{ id, name, label, type, value }`, where
1059
+ * `value` is itself a list of `{ value }` entries so a multi-select field can
1060
+ * carry several. Send every answer collected so far, not only what changed.
1061
+ */
1062
+ data: Record<string, unknown>[];
1063
+ /**
1064
+ * Optional progress information for a partially completed form, e.g.
1065
+ * `{ current_step, total_steps }`. Lets the server report how far a prospect
1066
+ * got before abandoning.
1067
+ */
1068
+ progress?: Record<string, unknown>;
1069
+ }
1070
+ /** Options for {@link IntakeSubmissions.uploadFile}. */
1071
+ interface UploadFileOptions {
1072
+ /** The session the file belongs to. */
1073
+ sessionId: string;
1074
+ /** The file to upload. */
1075
+ file: FileUpload;
1076
+ }
1077
+ /** Options for {@link IntakeSubmissions.initiateMultipartUpload}. */
1078
+ interface InitiateMultipartOptions {
1079
+ /** The session the file belongs to. */
1080
+ sessionId: string;
1081
+ /** Name to store the file under. */
1082
+ fileName: string;
1083
+ /** MIME type of the whole file. */
1084
+ mimeType: string;
1085
+ /** Total size of the whole file in bytes, not of one part. */
1086
+ fileSize: number;
1087
+ }
1088
+ /** Options for {@link IntakeSubmissions.uploadMultipartPart}. */
1089
+ interface UploadPartOptions {
1090
+ /** The upload id returned by initiate. */
1091
+ uploadId: string;
1092
+ /** 1-based part number, between 1 and 10000. */
1093
+ partNumber: number;
1094
+ /** The bytes of this part. */
1095
+ part: FileUpload;
1096
+ }
1097
+ /** One uploaded part, as {@link IntakeSubmissions.finishMultipartUpload} expects it. */
1098
+ interface MultipartPart {
1099
+ /** The part number that was uploaded. */
1100
+ part_number: number;
1101
+ /** The etag the server returned for that part. */
1102
+ etag: string;
1103
+ }
1104
+ /** Options for {@link IntakeSubmissions.finishMultipartUpload}. */
1105
+ interface FinishMultipartOptions {
1106
+ /** The upload id returned by initiate. */
1107
+ uploadId: string;
1108
+ /** Every uploaded part with its etag, in order. Must not be empty. */
1109
+ parts: MultipartPart[];
1110
+ }
1111
+ /** Options for {@link IntakeSubmissions.uploadLargeFile}. */
1112
+ interface UploadLargeFileOptions {
1113
+ /** The session the file belongs to. */
1114
+ sessionId: string;
1115
+ /** Path to the file on local disk. */
1116
+ filePath: string;
1117
+ /** Name to store the file under. Defaults to the path's base name. */
1118
+ fileName?: string;
1119
+ /** MIME type to send. Defaults to one derived from the file name. */
1120
+ mimeType?: string;
1121
+ /**
1122
+ * Bytes per part. Must be between {@link IntakeSubmissions.MIN_PART_SIZE} and
1123
+ * {@link IntakeSubmissions.MAX_PART_SIZE}. Defaults to
1124
+ * {@link IntakeSubmissions.DEFAULT_PART_SIZE}.
1125
+ */
1126
+ partSize?: number;
1127
+ }
1128
+ /**
1129
+ * Intake submissions - the answers a prospect gives to your questionnaires.
1130
+ *
1131
+ * This is where the clinical part of the order flow happens. Two questionnaires
1132
+ * run through this one resource: the short pre-qualifying form that decides
1133
+ * whether a prospect is eligible at all, and the full intake questionnaire a
1134
+ * clinician later reviews. Which one a call belongs to is carried entirely by the
1135
+ * {@link Event} it passes.
1136
+ *
1137
+ * Both follow the same rhythm. The `*Initiated` event goes to
1138
+ * {@link IntakeSubmissions.create}, which creates the server-side record. Every
1139
+ * later step goes to {@link IntakeSubmissions.update} with `*InProgress`, and the
1140
+ * final submission with `*Completed`. Sending progress updates as the prospect
1141
+ * moves through the form is what makes partial completion visible instead of
1142
+ * looking like an abandoned session:
1143
+ *
1144
+ * ```ts
1145
+ * await client.intakeSubmissions().create({
1146
+ * session,
1147
+ * event: Event.IntakeInitiated,
1148
+ * teleformId,
1149
+ * data: answersSoFar,
1150
+ * });
1151
+ *
1152
+ * await client.intakeSubmissions().update({
1153
+ * session,
1154
+ * event: Event.IntakeCompleted,
1155
+ * teleformId,
1156
+ * data: allAnswers,
1157
+ * });
1158
+ * ```
1159
+ *
1160
+ * `data` is cumulative: send every answer collected so far on each call, not just
1161
+ * the fields that changed. Answers that are files rather than values go through
1162
+ * the upload methods on this same resource.
1163
+ */
1164
+ declare class IntakeSubmissions extends AbstractResource {
1165
+ /**
1166
+ * @param transport The shared transport.
1167
+ */
1168
+ constructor(transport: Transport);
1169
+ /**
1170
+ * Fetches one intake submission and its recorded answers.
1171
+ *
1172
+ * The teleform id is required alongside the submission id because a submission
1173
+ * is only meaningful against the form it answers - the server uses it to
1174
+ * resolve field definitions for the stored values.
1175
+ *
1176
+ * @param options The submission id and the teleform it answers.
1177
+ * @returns The submission and its answers.
1178
+ * @throws {NotFoundError} If the submission does not exist.
1179
+ * @throws {ApiError} On any other non-2xx status.
1180
+ * @throws {TransportError} If the request never completed.
1181
+ */
1182
+ view<T = Record<string, unknown>>(options: IntakeViewOptions): Promise<Response$1<T>>;
1183
+ /**
1184
+ * Creates the intake-submission record for a session and records the first
1185
+ * answers.
1186
+ *
1187
+ * This is the call that opens the record, so it takes an `*Initiated` event -
1188
+ * `Event.PreQualifyingInitiated` or `Event.IntakeInitiated`. Every later step
1189
+ * goes to {@link IntakeSubmissions.update}. The server advances its order-flow
1190
+ * state machine on the event, so passing the wrong one puts the flow in the
1191
+ * wrong state rather than failing loudly.
1192
+ *
1193
+ * @param options The session, event, teleform, answers, and optional progress.
1194
+ * @returns The created submission.
1195
+ * @throws {ValidationError} If the answers or the event are rejected.
1196
+ * @throws {NotFoundError} If the session or teleform does not exist.
1197
+ * @throws {ApiError} On any other non-2xx status.
1198
+ * @throws {TransportError} If the request never completed.
1199
+ */
1200
+ create<T = Record<string, unknown>>(options: IntakeWriteOptions): Promise<Response$1<T>>;
1201
+ /**
1202
+ * Records further answers against an existing intake submission.
1203
+ *
1204
+ * Takes an `*InProgress` event for an intermediate step or a `*Completed` event
1205
+ * for the final submission. `data` is cumulative - send every answer collected
1206
+ * so far, since the server treats the payload as the current state of the form
1207
+ * rather than a patch.
1208
+ *
1209
+ * @param options The session, event, teleform, answers, and optional progress.
1210
+ * @returns The updated submission.
1211
+ * @throws {ValidationError} If the answers or the event are rejected.
1212
+ * @throws {NotFoundError} If no submission exists for the session.
1213
+ * @throws {ApiError} On any other non-2xx status.
1214
+ * @throws {TransportError} If the request never completed.
1215
+ */
1216
+ update<T = Record<string, unknown>>(options: IntakeWriteOptions): Promise<Response$1<T>>;
1217
+ /** Smallest part the multipart endpoint accepts: 5 MB. */
1218
+ static readonly MIN_PART_SIZE: number;
1219
+ /** Largest part the multipart endpoint accepts: 25 MB. */
1220
+ static readonly MAX_PART_SIZE: number;
1221
+ /** Part size {@link IntakeSubmissions.uploadLargeFile} uses by default: 10 MB. */
1222
+ static readonly DEFAULT_PART_SIZE: number;
1223
+ /**
1224
+ * Uploads one file in a single request, for a file-type answer.
1225
+ *
1226
+ * Use this for the sizes a single request can carry - a photograph of an ID, a
1227
+ * PDF. The whole file is held in memory and sent as one `multipart/form-data`
1228
+ * body. For anything large enough to risk a timeout, use
1229
+ * {@link IntakeSubmissions.uploadLargeFile} instead.
1230
+ *
1231
+ * The response carries the stored file's reference, which is what you place in
1232
+ * the `value` of the corresponding field when you next call
1233
+ * {@link IntakeSubmissions.update}.
1234
+ *
1235
+ * @param options The session and the file.
1236
+ * @returns The stored file's reference.
1237
+ * @throws {ValidationError} If the file type or size is rejected.
1238
+ * @throws {NotFoundError} If the session does not exist.
1239
+ * @throws {ApiError} On any other non-2xx status.
1240
+ * @throws {TransportError} If the request never completed.
1241
+ */
1242
+ uploadFile<T = Record<string, unknown>>(options: UploadFileOptions): Promise<Response$1<T>>;
1243
+ /**
1244
+ * Opens a multipart upload and returns the id the parts will be sent under.
1245
+ *
1246
+ * The first of the four low-level multipart calls. Declare the *whole* file's
1247
+ * name, type, and size here - the server uses the size to validate the parts it
1248
+ * later receives. Prefer {@link IntakeSubmissions.uploadLargeFile}, which
1249
+ * drives all four calls; reach for these directly only when you need to
1250
+ * control the loop, for instance to upload parts from several workers.
1251
+ *
1252
+ * @param options The session and the whole file's metadata.
1253
+ * @returns The opened upload; `data().upload_id` carries the id.
1254
+ * @throws {ValidationError} If the metadata is rejected.
1255
+ * @throws {ApiError} On any other non-2xx status.
1256
+ * @throws {TransportError} If the request never completed.
1257
+ */
1258
+ initiateMultipartUpload<T = Record<string, unknown>>(options: InitiateMultipartOptions): Promise<Response$1<T>>;
1259
+ /**
1260
+ * Uploads one part of an open multipart upload.
1261
+ *
1262
+ * Part numbers are 1-based and must arrive with the part they label; the
1263
+ * response carries an etag you must keep, because
1264
+ * {@link IntakeSubmissions.finishMultipartUpload} needs every part's etag to
1265
+ * assemble the file. Parts may be uploaded in any order.
1266
+ *
1267
+ * @param options The upload id, the part number, and the part's bytes.
1268
+ * @returns The stored part; `data().etag` carries the etag.
1269
+ * @throws {TypeError} If `partNumber` is outside 1–10000.
1270
+ * @throws {NotFoundError} If the upload id is unknown or already finished.
1271
+ * @throws {ApiError} On any other non-2xx status.
1272
+ * @throws {TransportError} If the request never completed.
1273
+ */
1274
+ uploadMultipartPart<T = Record<string, unknown>>(options: UploadPartOptions): Promise<Response$1<T>>;
1275
+ /**
1276
+ * Closes a multipart upload, assembling the parts into one stored file.
1277
+ *
1278
+ * Send every part with the etag its own upload returned. The server verifies
1279
+ * the set against what it received, so a missing or mismatched part fails here
1280
+ * rather than producing a corrupt file.
1281
+ *
1282
+ * @param options The upload id and every part with its etag.
1283
+ * @returns The stored file's reference.
1284
+ * @throws {TypeError} If `parts` is empty.
1285
+ * @throws {ValidationError} If the parts do not match what the server holds.
1286
+ * @throws {NotFoundError} If the upload id is unknown.
1287
+ * @throws {ApiError} On any other non-2xx status.
1288
+ * @throws {TransportError} If the request never completed.
1289
+ */
1290
+ finishMultipartUpload<T = Record<string, unknown>>(options: FinishMultipartOptions): Promise<Response$1<T>>;
1291
+ /**
1292
+ * Abandons a multipart upload and discards the parts already stored.
1293
+ *
1294
+ * Call this when an upload cannot be completed, so the parts do not linger.
1295
+ * {@link IntakeSubmissions.uploadLargeFile} does it for you on failure.
1296
+ *
1297
+ * @param uploadId The upload id to abandon.
1298
+ * @returns The abort confirmation.
1299
+ * @throws {NotFoundError} If the upload id is unknown or already finished.
1300
+ * @throws {ApiError} On any other non-2xx status.
1301
+ * @throws {TransportError} If the request never completed.
1302
+ */
1303
+ abortMultipartUpload<T = Record<string, unknown>>(uploadId: string): Promise<Response$1<T>>;
1304
+ /**
1305
+ * Uploads a large file from disk, driving the whole multipart flow.
1306
+ *
1307
+ * This is the method to reach for when a file is too big for one request. It
1308
+ * streams the file one part at a time, so a 200 MB video costs one part of
1309
+ * memory rather than 200 MB: initiate, then read a part, upload it, keep its
1310
+ * etag, repeat, then finish. If any step fails the upload is aborted so no
1311
+ * parts are left behind, and the failure that broke the upload is what
1312
+ * surfaces - not the abort's own outcome.
1313
+ *
1314
+ * The first part fixes the name and MIME type for the whole upload, so
1315
+ * `initiate` and every part agree.
1316
+ *
1317
+ * ```ts
1318
+ * const stored = await client.intakeSubmissions().uploadLargeFile({
1319
+ * sessionId,
1320
+ * filePath: '/var/uploads/consult-video.mp4',
1321
+ * });
1322
+ * ```
1323
+ *
1324
+ * @param options The session, the file's path, and optional name, MIME type,
1325
+ * and part size.
1326
+ * @returns The finished upload's response - the same one
1327
+ * {@link IntakeSubmissions.finishMultipartUpload} returns.
1328
+ * @throws {TypeError} If `partSize` is outside the allowed range, or if the
1329
+ * file is missing, unreadable, or empty.
1330
+ * @throws {ApiError} If the server response omits a usable `upload_id` or
1331
+ * `etag`, or on any other non-2xx status.
1332
+ * @throws {TransportError} If a request never completed.
1333
+ */
1334
+ uploadLargeFile<T = Record<string, unknown>>(options: UploadLargeFileOptions): Promise<Response$1<T>>;
1335
+ }
1336
+
1337
+ /**
1338
+ * Lab tests - the diagnostic panels a product may require.
1339
+ *
1340
+ * Some treatments cannot be prescribed without a result: a blood panel, a
1341
+ * hormone level. A lab test is the server-side definition of one such
1342
+ * requirement, and products reference the tests they depend on. A storefront reads
1343
+ * this to tell a buyer, before they pay, that a test will be part of their
1344
+ * treatment.
1345
+ *
1346
+ * Read-only, and configured in your AsterMD dashboard.
1347
+ */
1348
+ declare class LabTests extends AbstractResource {
1349
+ /**
1350
+ * @param transport The shared transport.
1351
+ */
1352
+ constructor(transport: Transport);
1353
+ /**
1354
+ * Lists lab tests, filtered and paginated.
1355
+ *
1356
+ * Pagination details come back in `meta()`. The accepted filters are in the
1357
+ * API reference in your AsterMD dashboard.
1358
+ *
1359
+ * @param query Filters and pagination parameters.
1360
+ * @returns The matching lab tests, with pagination in `meta()`.
1361
+ * @throws {ValidationError} If a filter is rejected.
1362
+ * @throws {ApiError} On any other non-2xx status.
1363
+ * @throws {TransportError} If the request never completed.
1364
+ */
1365
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
1366
+ /**
1367
+ * Fetches one lab test.
1368
+ *
1369
+ * @param id The lab test's id.
1370
+ * @returns The lab test record.
1371
+ * @throws {NotFoundError} If the lab test does not exist or is out of scope.
1372
+ * @throws {ApiError} On any other non-2xx status.
1373
+ * @throws {TransportError} If the request never completed.
1374
+ */
1375
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1376
+ }
1377
+
1378
+ /**
1379
+ * Medications - the reference list of drugs the platform knows about.
1380
+ *
1381
+ * This is a lookup table, not a catalogue of things to sell: it is what a
1382
+ * storefront renders when an intake form asks "which medications are you
1383
+ * currently taking?" Answers picked from this list arrive at the clinician as
1384
+ * recognised drugs rather than free text, which is the difference between a
1385
+ * reviewable history and a paragraph someone has to interpret.
1386
+ *
1387
+ * List-only by design - the API exposes no single-medication endpoint, so
1388
+ * neither does the SDK. Filter through the query parameters to implement a
1389
+ * type-ahead rather than downloading the whole list.
1390
+ */
1391
+ declare class Medications extends AbstractResource {
1392
+ /**
1393
+ * @param transport The shared transport.
1394
+ */
1395
+ constructor(transport: Transport);
1396
+ /**
1397
+ * Lists medications, filtered and paginated.
1398
+ *
1399
+ * Intended to back a search or type-ahead on an intake form. Pagination comes
1400
+ * back in `meta()`; the accepted filters are in the API reference in your
1401
+ * AsterMD dashboard.
1402
+ *
1403
+ * @param query Filters and pagination parameters.
1404
+ * @returns The matching medications, with pagination in `meta()`.
1405
+ * @throws {ValidationError} If a filter is rejected.
1406
+ * @throws {ApiError} On any other non-2xx status.
1407
+ * @throws {TransportError} If the request never completed.
1408
+ */
1409
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
1410
+ }
1411
+
1412
+ /**
1413
+ * Opportunities - a prospect who has identified themselves and is worth
1414
+ * following up.
1415
+ *
1416
+ * This is the commercial record of the order flow, and the point where an
1417
+ * anonymous session becomes a named person. Create it once you have a name and a
1418
+ * contact address, typically right after the pre-qualifying form, and attach the
1419
+ * sessions that led to it so the whole journey stays connected:
1420
+ *
1421
+ * ```ts
1422
+ * const opportunity = await client.opportunities().create({
1423
+ * first_name: 'Jane',
1424
+ * email: 'jane@example.test',
1425
+ * sessions: [session],
1426
+ * });
1427
+ * ```
1428
+ *
1429
+ * Attaching sessions is what makes attribution work: without them the server has
1430
+ * a lead with no idea where it came from. An opportunity can carry several
1431
+ * sessions, which is what you want when a prospect returns across devices or
1432
+ * days.
1433
+ *
1434
+ * Where this record is commercial, {@link Patients} is its clinical counterpart -
1435
+ * created later, once the prospect is actually proceeding with treatment.
1436
+ */
1437
+ declare class Opportunities extends AbstractResource {
1438
+ /**
1439
+ * @param transport The shared transport.
1440
+ */
1441
+ constructor(transport: Transport);
1442
+ /**
1443
+ * Creates an opportunity.
1444
+ *
1445
+ * Include a `sessions` array of the session identifiers that led here, so the
1446
+ * server can attribute the lead. The rest of the payload is forwarded
1447
+ * unmodified.
1448
+ *
1449
+ * @param data The prospect's details and the sessions to attach.
1450
+ * @returns The created opportunity, including its id.
1451
+ * @throws {ValidationError} If a field is missing or rejected.
1452
+ * @throws {ApiError} On any other non-2xx status.
1453
+ * @throws {TransportError} If the request never completed.
1454
+ */
1455
+ create<T = Record<string, unknown>>(data: Record<string, unknown>): Promise<Response$1<T>>;
1456
+ /**
1457
+ * Fetches one opportunity.
1458
+ *
1459
+ * @param id The opportunity's id.
1460
+ * @returns The opportunity record.
1461
+ * @throws {NotFoundError} If the opportunity does not exist or is out of scope.
1462
+ * @throws {ApiError} On any other non-2xx status.
1463
+ * @throws {TransportError} If the request never completed.
1464
+ */
1465
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1466
+ /**
1467
+ * Updates an opportunity.
1468
+ *
1469
+ * Send only the fields you are changing. Use this to enrich a lead as more
1470
+ * information arrives - a phone number, a later session to attach.
1471
+ *
1472
+ * @param id The opportunity's id.
1473
+ * @param data The fields to change.
1474
+ * @returns The updated opportunity.
1475
+ * @throws {NotFoundError} If the opportunity does not exist.
1476
+ * @throws {ValidationError} If a field is rejected.
1477
+ * @throws {ApiError} On any other non-2xx status.
1478
+ * @throws {TransportError} If the request never completed.
1479
+ */
1480
+ update<T = Record<string, unknown>>(id: string, data: Record<string, unknown>): Promise<Response$1<T>>;
1481
+ /**
1482
+ * Changes an opportunity's status.
1483
+ *
1484
+ * Kept separate from {@link Opportunities.update} so a pipeline transition is
1485
+ * explicit in your code and in the server's audit trail. The accepted values
1486
+ * are configured for your organisation; see the API reference in your AsterMD
1487
+ * dashboard.
1488
+ *
1489
+ * @param id The opportunity's id.
1490
+ * @param status The new status value, sent as `opportunity_status`.
1491
+ * @returns The updated opportunity.
1492
+ * @throws {NotFoundError} If the opportunity does not exist.
1493
+ * @throws {ValidationError} If the status is not an accepted value.
1494
+ * @throws {ApiError} On any other non-2xx status.
1495
+ * @throws {TransportError} If the request never completed.
1496
+ */
1497
+ status<T = Record<string, unknown>>(id: string, status: string): Promise<Response$1<T>>;
1498
+ }
1499
+
1500
+ /** Options for {@link Patients.submitHealthInformation}. */
1501
+ interface SubmitHealthInformationOptions {
1502
+ /**
1503
+ * The health information to record. Forwarded unmodified; see the API
1504
+ * reference in your AsterMD dashboard for the accepted fields.
1505
+ */
1506
+ data: Record<string, unknown>;
1507
+ /**
1508
+ * Verification token proving the patient authorised this submission, sent as
1509
+ * the `x-phi-verification-token` header. Required by the server for
1510
+ * submissions that need explicit patient confirmation.
1511
+ */
1512
+ phiVerificationToken?: string;
1513
+ }
1514
+ /**
1515
+ * Patients - the person receiving care, and the most sensitive data in the SDK.
1516
+ *
1517
+ * A patient record is created once the prospect has qualified and is proceeding
1518
+ * with treatment, and it is what a clinician's review, a prescription, and an
1519
+ * order all hang from. Where an {@link Opportunities} record is a commercial
1520
+ * artifact, this is a clinical one.
1521
+ *
1522
+ * Everything on this resource carries personal and protected health information.
1523
+ * Two consequences worth knowing about:
1524
+ *
1525
+ * The SDK never writes a request or response body from any `patients/*` endpoint
1526
+ * to a debug log, even with `debugRedact: false` - those bodies are dropped
1527
+ * wholesale rather than masked. Debug entries for these calls show the method,
1528
+ * URL, and status only.
1529
+ *
1530
+ * Health-information submission is a two-step flow when the server requires
1531
+ * explicit patient confirmation: {@link Patients.verifyHealthInformationOtp}
1532
+ * confirms a one-time code the patient received, and the resulting token is
1533
+ * passed to {@link Patients.submitHealthInformation} as
1534
+ * `phiVerificationToken`.
1535
+ *
1536
+ * Handling this data does not by itself make your application HIPAA compliant -
1537
+ * that depends on your own infrastructure, policies, and agreements. Contact
1538
+ * info@astermd.com regarding a Business Associate Agreement.
1539
+ */
1540
+ declare class Patients extends AbstractResource {
1541
+ /**
1542
+ * @param transport The shared transport.
1543
+ */
1544
+ constructor(transport: Transport);
1545
+ /**
1546
+ * Creates a patient record.
1547
+ *
1548
+ * Call this once the prospect has qualified and is proceeding. The payload is
1549
+ * forwarded unmodified, so it carries whatever demographic and contact fields
1550
+ * your organisation's configuration requires.
1551
+ *
1552
+ * @param data The patient's details. See the API reference in your AsterMD
1553
+ * dashboard for the accepted fields.
1554
+ * @returns The created patient; `data()` carries the new record including its id.
1555
+ * @throws {ValidationError} If a field is missing or rejected.
1556
+ * @throws {ApiError} On any other non-2xx status.
1557
+ * @throws {TransportError} If the request never completed.
1558
+ */
1559
+ create<T = Record<string, unknown>>(data: Record<string, unknown>): Promise<Response$1<T>>;
1560
+ /**
1561
+ * Fetches one patient record.
1562
+ *
1563
+ * Returns the record as your credentials are permitted to see it. Because the
1564
+ * organisation scope lives inside the JWT, an id belonging to another
1565
+ * organisation is indistinguishable from one that never existed - both raise
1566
+ * {@link NotFoundError}.
1567
+ *
1568
+ * @param id The patient's id.
1569
+ * @returns The patient record.
1570
+ * @throws {NotFoundError} If the patient does not exist or is out of scope.
1571
+ * @throws {ApiError} On any other non-2xx status.
1572
+ * @throws {TransportError} If the request never completed.
1573
+ */
1574
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1575
+ /**
1576
+ * Updates a patient record.
1577
+ *
1578
+ * Send only the fields you are changing; the server merges them into the
1579
+ * existing record rather than replacing it.
1580
+ *
1581
+ * @param id The patient's id.
1582
+ * @param data The fields to change.
1583
+ * @returns The updated patient record.
1584
+ * @throws {NotFoundError} If the patient does not exist.
1585
+ * @throws {ValidationError} If a field is rejected.
1586
+ * @throws {ApiError} On any other non-2xx status.
1587
+ * @throws {TransportError} If the request never completed.
1588
+ */
1589
+ update<T = Record<string, unknown>>(id: string, data: Record<string, unknown>): Promise<Response$1<T>>;
1590
+ /**
1591
+ * Changes a patient's status.
1592
+ *
1593
+ * A narrow call for the one field that drives workflow state, kept separate
1594
+ * from {@link Patients.update} so a status transition is explicit in your code
1595
+ * and in the server's audit trail. The accepted values are configured for your
1596
+ * organisation; see the API reference in your AsterMD dashboard.
1597
+ *
1598
+ * @param id The patient's id.
1599
+ * @param status The new status value.
1600
+ * @returns The updated patient record.
1601
+ * @throws {NotFoundError} If the patient does not exist.
1602
+ * @throws {ValidationError} If the status is not an accepted value.
1603
+ * @throws {ApiError} On any other non-2xx status.
1604
+ * @throws {TransportError} If the request never completed.
1605
+ */
1606
+ status<T = Record<string, unknown>>(id: string, status: string): Promise<Response$1<T>>;
1607
+ /**
1608
+ * Submits a patient's health information.
1609
+ *
1610
+ * This is the clinical questionnaire result a clinician reviews, and the most
1611
+ * sensitive payload the SDK carries. Where the server requires explicit patient
1612
+ * confirmation, obtain a token through
1613
+ * {@link Patients.verifyHealthInformationOtp} first and pass it as
1614
+ * `phiVerificationToken`.
1615
+ *
1616
+ * Neither the request nor the response body is ever written to a debug log.
1617
+ *
1618
+ * @param options The health information, and the verification token where one
1619
+ * is required.
1620
+ * @returns The stored submission.
1621
+ * @throws {AuthenticationError} If the verification token is missing or invalid.
1622
+ * @throws {ValidationError} If the payload is rejected.
1623
+ * @throws {ApiError} On any other non-2xx status.
1624
+ * @throws {TransportError} If the request never completed.
1625
+ */
1626
+ submitHealthInformation<T = Record<string, unknown>>(options: SubmitHealthInformationOptions): Promise<Response$1<T>>;
1627
+ /**
1628
+ * Verifies the one-time code a patient was sent, before health information is
1629
+ * submitted.
1630
+ *
1631
+ * The confirming half of the two-step health-information flow. On success the
1632
+ * response carries the verification token to pass to
1633
+ * {@link Patients.submitHealthInformation}.
1634
+ *
1635
+ * The one-time code is a credential: it is never written to a debug log, and it
1636
+ * should not be written to yours either.
1637
+ *
1638
+ * @param data The patient identifier and the one-time code. See the API
1639
+ * reference in your AsterMD dashboard for the exact fields.
1640
+ * @returns The verification result, carrying the token on success.
1641
+ * @throws {ValidationError} If the code is wrong, expired, or malformed.
1642
+ * @throws {ApiError} On any other non-2xx status.
1643
+ * @throws {TransportError} If the request never completed.
1644
+ */
1645
+ verifyHealthInformationOtp<T = Record<string, unknown>>(data: Record<string, unknown>): Promise<Response$1<T>>;
1646
+ }
1647
+
1648
+ /**
1649
+ * Products - the read-only catalogue your storefront sells from.
1650
+ *
1651
+ * A product carries everything a storefront needs to render it: names,
1652
+ * descriptions, images, variants, prices, purchase quantity limits, and the
1653
+ * questionnaire a buyer must complete. All of it is configured in your AsterMD
1654
+ * dashboard, and the SDK only reads it - a storefront never creates or edits a
1655
+ * product.
1656
+ *
1657
+ * Which products a given selling surface may offer is a channel decision, so
1658
+ * {@link Channels.assignedProducts} and {@link Channels.details} are usually the
1659
+ * better starting point for a catalogue page. Reach for this resource when you
1660
+ * need the full product list regardless of channel, or one product by id.
1661
+ *
1662
+ * Image and asset paths come back relative. Resolve them with
1663
+ * `client.config().assetUrl(path)`, and treat the result as a fetch URL: download
1664
+ * the asset once and serve your own copy rather than hot-linking.
1665
+ */
1666
+ declare class Products extends AbstractResource {
1667
+ /**
1668
+ * @param transport The shared transport.
1669
+ */
1670
+ constructor(transport: Transport);
1671
+ /**
1672
+ * Lists products, filtered and paginated.
1673
+ *
1674
+ * Pagination details come back in `meta()`. The accepted filters are in the
1675
+ * API reference in your AsterMD dashboard.
1676
+ *
1677
+ * @param query Filters and pagination parameters.
1678
+ * @returns The matching products, with pagination in `meta()`.
1679
+ * @throws {ValidationError} If a filter is rejected.
1680
+ * @throws {ApiError} On any other non-2xx status.
1681
+ * @throws {TransportError} If the request never completed.
1682
+ */
1683
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
1684
+ /**
1685
+ * Fetches one product, including its variants and prices.
1686
+ *
1687
+ * @param id The product's id.
1688
+ * @returns The product record.
1689
+ * @throws {NotFoundError} If the product does not exist or is out of scope.
1690
+ * @throws {ApiError} On any other non-2xx status.
1691
+ * @throws {TransportError} If the request never completed.
1692
+ */
1693
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1694
+ }
1695
+
1696
+ /** Options for {@link Sessions.create}. */
1697
+ interface SessionCreateOptions {
1698
+ /**
1699
+ * Attribution and context to record against the session - UTM parameters,
1700
+ * referrer, landing page. Forwarded unmodified; see the API reference in your
1701
+ * AsterMD dashboard for the recognised keys.
1702
+ */
1703
+ data?: Record<string, unknown>;
1704
+ /** The visitor's user agent, forwarded as the `User-Agent` header. */
1705
+ userAgent?: string;
1706
+ /** The visitor's IP address, forwarded as the `X-Original-Client-Ip` header. */
1707
+ clientIp?: string;
1708
+ }
1709
+ /** Options for {@link Sessions.view}. */
1710
+ interface SessionViewOptions {
1711
+ /** Session identifiers to fetch. Sent as a comma-separated `session_ids` query. */
1712
+ sessions: string[];
1713
+ /** IANA time zone used to render timestamps in the response. */
1714
+ tz?: string;
1715
+ }
1716
+ /**
1717
+ * Sessions - the thread that ties one visitor's whole journey together.
1718
+ *
1719
+ * A session is the first thing an order flow creates and the value every later
1720
+ * call is attached to: intake submissions, the cart, checkout events, and finally
1721
+ * the opportunity. Creating one also records an implicit `visit_page` event
1722
+ * server-side, so the funnel starts measuring from that moment.
1723
+ *
1724
+ * Create the session as early as the visitor's first page view, keep the returned
1725
+ * identifier for the length of their visit - in a cookie or your own server-side
1726
+ * session - and pass it to every subsequent call:
1727
+ *
1728
+ * ```ts
1729
+ * const { session } = (
1730
+ * await client.sessions().create<{ session: string }>({
1731
+ * userAgent: request.headers['user-agent'],
1732
+ * clientIp: request.ip,
1733
+ * })
1734
+ * ).data();
1735
+ * ```
1736
+ *
1737
+ * Passing the visitor's user agent and IP matters: the server uses them for
1738
+ * attribution and geographic checks, and without them every session appears to
1739
+ * originate from your server rather than from the visitor.
1740
+ */
1741
+ declare class Sessions extends AbstractResource {
1742
+ /**
1743
+ * @param transport The shared transport.
1744
+ */
1745
+ constructor(transport: Transport);
1746
+ /**
1747
+ * Creates a new session and returns its identifier.
1748
+ *
1749
+ * This is the first call of an order flow. The server records an implicit
1750
+ * `visit_page` event alongside the new session, so the funnel begins here.
1751
+ * Inspect `data().session` on the response for the identifier to thread through
1752
+ * every later call.
1753
+ *
1754
+ * @param options Attribution payload and visitor headers. All optional; with
1755
+ * none, an empty object body is sent.
1756
+ * @returns The created session; `data()` holds `{ session }`.
1757
+ * @throws {ValidationError} If the attribution payload is rejected.
1758
+ * @throws {AuthenticationError} If the credentials are refused.
1759
+ * @throws {RateLimitError} If the organisation's request allowance is exceeded.
1760
+ * @throws {ApiError} On any other non-2xx status.
1761
+ * @throws {TransportError} If the request never completed.
1762
+ */
1763
+ create<T = Record<string, unknown>>(options?: SessionCreateOptions): Promise<Response$1<T>>;
1764
+ /**
1765
+ * Fetches one or more sessions and the events recorded against them.
1766
+ *
1767
+ * Useful for reconstructing a visitor's journey server-side - which forms they
1768
+ * touched, where they stopped. Several identifiers can be fetched in one call,
1769
+ * which is cheaper than looping. Timestamps come back in `tz` when you supply
1770
+ * one, and in the organisation's default zone otherwise.
1771
+ *
1772
+ * @param options The session identifiers to fetch, and an optional time zone.
1773
+ * @returns The sessions and their recorded events.
1774
+ * @throws {NotFoundError} If none of the identifiers resolve.
1775
+ * @throws {ApiError} On any other non-2xx status.
1776
+ * @throws {TransportError} If the request never completed.
1777
+ */
1778
+ view<T = Record<string, unknown>>(options: SessionViewOptions): Promise<Response$1<T>>;
1779
+ /**
1780
+ * Updates the attribution and context recorded against an existing session.
1781
+ *
1782
+ * Call this when information arrives after the session was created - a UTM
1783
+ * parameter only present on a later page, a referrer resolved asynchronously.
1784
+ * The payload is merged server-side rather than replacing what is already
1785
+ * recorded.
1786
+ *
1787
+ * @param session The session identifier to update.
1788
+ * @param data Fields to record. Forwarded unmodified; see the API reference in
1789
+ * your AsterMD dashboard for the recognised keys.
1790
+ * @returns The updated session.
1791
+ * @throws {NotFoundError} If the session does not exist.
1792
+ * @throws {ValidationError} If the payload is rejected.
1793
+ * @throws {ApiError} On any other non-2xx status.
1794
+ * @throws {TransportError} If the request never completed.
1795
+ */
1796
+ update<T = Record<string, unknown>>(session: string, data?: Record<string, unknown>): Promise<Response$1<T>>;
1797
+ /**
1798
+ * Deletes a session and the events recorded against it.
1799
+ *
1800
+ * Intended for honouring a visitor's erasure request, not for ordinary
1801
+ * cleanup - sessions age out server-side on their own. Deleting one removes the
1802
+ * attribution trail for anything that referenced it.
1803
+ *
1804
+ * @param session The session identifier to delete.
1805
+ * @returns The deletion confirmation.
1806
+ * @throws {NotFoundError} If the session does not exist.
1807
+ * @throws {ApiError} On any other non-2xx status.
1808
+ * @throws {TransportError} If the request never completed.
1809
+ */
1810
+ delete<T = Record<string, unknown>>(session: string): Promise<Response$1<T>>;
1811
+ }
1812
+
1813
+ /**
1814
+ * Shipping options - the delivery methods available for an order.
1815
+ *
1816
+ * Each option carries its name, price, and expected delivery window, which is
1817
+ * what a checkout page needs to let a buyer choose. Which options apply can depend
1818
+ * on the products in the cart and on the destination, so fetch them at checkout
1819
+ * rather than hardcoding a list.
1820
+ *
1821
+ * Read-only, and configured in your AsterMD dashboard.
1822
+ */
1823
+ declare class Shippings extends AbstractResource {
1824
+ /**
1825
+ * @param transport The shared transport.
1826
+ */
1827
+ constructor(transport: Transport);
1828
+ /**
1829
+ * Lists shipping options, filtered and paginated.
1830
+ *
1831
+ * Pagination details come back in `meta()`. The accepted filters are in the
1832
+ * API reference in your AsterMD dashboard.
1833
+ *
1834
+ * @param query Filters and pagination parameters.
1835
+ * @returns The matching shipping options, with pagination in `meta()`.
1836
+ * @throws {ValidationError} If a filter is rejected.
1837
+ * @throws {ApiError} On any other non-2xx status.
1838
+ * @throws {TransportError} If the request never completed.
1839
+ */
1840
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
1841
+ /**
1842
+ * Fetches one shipping option.
1843
+ *
1844
+ * @param id The shipping option's id.
1845
+ * @returns The shipping option record.
1846
+ * @throws {NotFoundError} If the shipping option does not exist or is out of scope.
1847
+ * @throws {ApiError} On any other non-2xx status.
1848
+ * @throws {TransportError} If the request never completed.
1849
+ */
1850
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1851
+ }
1852
+
1853
+ /**
1854
+ * Teleforms - the questionnaire definitions your storefront renders.
1855
+ *
1856
+ * A teleform is the server-side definition of a form: its fields, their types,
1857
+ * their order, and the validation the clinician's workflow expects. Both the
1858
+ * pre-qualifying questionnaire and the full intake questionnaire are teleforms.
1859
+ * Fetch the definition, render it in your own UI, and submit the answers through
1860
+ * {@link IntakeSubmissions} together with the teleform's id.
1861
+ *
1862
+ * There are two ways in. {@link Teleforms.viewByIdentifier} takes the stable
1863
+ * human-readable identifier you configured in your AsterMD dashboard, which is
1864
+ * what a storefront normally has hardcoded or in configuration.
1865
+ * {@link Teleforms.view} takes the generated id, which is what appears in API
1866
+ * responses elsewhere.
1867
+ *
1868
+ * Definitions change when someone edits the form in the dashboard, so fetch them
1869
+ * rather than embedding a copy - but they change rarely, so caching the result
1870
+ * for minutes rather than seconds is reasonable.
1871
+ */
1872
+ declare class Teleforms extends AbstractResource {
1873
+ /**
1874
+ * @param transport The shared transport.
1875
+ */
1876
+ constructor(transport: Transport);
1877
+ /**
1878
+ * Fetches a teleform definition by its configured identifier.
1879
+ *
1880
+ * This is the call a storefront usually wants: the identifier is the stable
1881
+ * name you chose in your AsterMD dashboard, so it can live in your
1882
+ * configuration and survive the form being edited.
1883
+ *
1884
+ * @param identifier The teleform's configured identifier.
1885
+ * @returns The teleform definition.
1886
+ * @throws {NotFoundError} If no teleform carries that identifier.
1887
+ * @throws {ApiError} On any other non-2xx status.
1888
+ * @throws {TransportError} If the request never completed.
1889
+ */
1890
+ viewByIdentifier<T = Record<string, unknown>>(identifier: string): Promise<Response$1<T>>;
1891
+ /**
1892
+ * Fetches a teleform definition by its generated id.
1893
+ *
1894
+ * Use this when you already hold an id from another API response - a product's
1895
+ * assigned teleform, for instance - rather than a configured identifier.
1896
+ *
1897
+ * @param id The teleform's generated id.
1898
+ * @returns The teleform definition.
1899
+ * @throws {NotFoundError} If the teleform does not exist.
1900
+ * @throws {ApiError} On any other non-2xx status.
1901
+ * @throws {TransportError} If the request never completed.
1902
+ */
1903
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1904
+ }
1905
+
1906
+ /** Options for {@link Treatments.sync}. */
1907
+ interface TreatmentSyncOptions {
1908
+ /** The session the orders belong to. */
1909
+ session: string;
1910
+ /** Order identifiers from your own commerce system. */
1911
+ orderIds: string[];
1912
+ /** Campaign attribution to record alongside the orders. */
1913
+ utmSource?: string;
1914
+ /** The visitor's user agent, forwarded as the `User-Agent` header. */
1915
+ userAgent?: string;
1916
+ }
1917
+ /**
1918
+ * Treatments - the order, once money has actually changed hands.
1919
+ *
1920
+ * A treatment is the end of the order flow and the start of the clinical one:
1921
+ * the record a clinician reviews, prescribes against, and that fulfilment works
1922
+ * from. Create it *after* your own payment flow has settled, never before - the
1923
+ * SDK does not process payments, and a treatment recorded against an unsettled
1924
+ * payment puts an order into the clinical queue that may never be paid for.
1925
+ *
1926
+ * There are two ways to record one. {@link Treatments.create} takes the order
1927
+ * explicitly, which is what you want when your storefront owns checkout.
1928
+ * {@link Treatments.sync} takes a session and a list of order identifiers from
1929
+ * your own commerce system, which is what you want when checkout happened
1930
+ * elsewhere and you are reconciling after the fact.
1931
+ *
1932
+ * ```ts
1933
+ * await client.treatments().create({
1934
+ * external_order_id: 'EXT-123',
1935
+ * patient: { id: patientId },
1936
+ * product: { id: productId },
1937
+ * });
1938
+ * ```
1939
+ */
1940
+ declare class Treatments extends AbstractResource {
1941
+ /**
1942
+ * @param transport The shared transport.
1943
+ */
1944
+ constructor(transport: Transport);
1945
+ /**
1946
+ * Records a treatment - an order that has been paid for.
1947
+ *
1948
+ * Call this only after your payment flow has settled. Include your own order
1949
+ * identifier as `external_order_id` so the record can be reconciled against
1950
+ * your commerce system later.
1951
+ *
1952
+ * @param data The order: patient, product, your order identifier, and whatever
1953
+ * else your configuration requires.
1954
+ * @returns The created treatment.
1955
+ * @throws {ValidationError} If a field is missing or rejected.
1956
+ * @throws {NotFoundError} If the referenced patient or product does not exist.
1957
+ * @throws {ApiError} On any other non-2xx status.
1958
+ * @throws {TransportError} If the request never completed.
1959
+ */
1960
+ create<T = Record<string, unknown>>(data: Record<string, unknown>): Promise<Response$1<T>>;
1961
+ /**
1962
+ * Fetches one treatment, including its clinical status.
1963
+ *
1964
+ * This is how a storefront shows an order's progress - whether a clinician has
1965
+ * reviewed it, whether it has shipped.
1966
+ *
1967
+ * @param id The treatment's id.
1968
+ * @returns The treatment record.
1969
+ * @throws {NotFoundError} If the treatment does not exist or is out of scope.
1970
+ * @throws {ApiError} On any other non-2xx status.
1971
+ * @throws {TransportError} If the request never completed.
1972
+ */
1973
+ view<T = Record<string, unknown>>(id: string): Promise<Response$1<T>>;
1974
+ /**
1975
+ * Lists treatments, filtered and paginated.
1976
+ *
1977
+ * Useful for an account area showing a patient's order history. Pagination
1978
+ * details come back in `meta()`; the accepted filters are in the API reference
1979
+ * in your AsterMD dashboard.
1980
+ *
1981
+ * @param query Filters and pagination parameters.
1982
+ * @returns The matching treatments, with pagination in `meta()`.
1983
+ * @throws {ValidationError} If a filter is rejected.
1984
+ * @throws {ApiError} On any other non-2xx status.
1985
+ * @throws {TransportError} If the request never completed.
1986
+ */
1987
+ list<T = Record<string, unknown>>(query?: QueryParams): Promise<Response$1<T>>;
1988
+ /**
1989
+ * Reconciles orders placed in your own commerce system against a session.
1990
+ *
1991
+ * The alternative to {@link Treatments.create} for flows where checkout happens
1992
+ * outside your storefront: hand over the session and your order identifiers and
1993
+ * the server creates the treatments and attributes them to that session's
1994
+ * journey. Several orders can be reconciled in one call.
1995
+ *
1996
+ * @param options The session, your order identifiers, and optional attribution.
1997
+ * @returns The created treatments.
1998
+ * @throws {ValidationError} If the session or an order identifier is rejected.
1999
+ * @throws {NotFoundError} If the session does not exist.
2000
+ * @throws {ApiError} On any other non-2xx status.
2001
+ * @throws {TransportError} If the request never completed.
2002
+ */
2003
+ sync<T = Record<string, unknown>>(options: TreatmentSyncOptions): Promise<Response$1<T>>;
2004
+ }
2005
+
2006
+ /**
2007
+ * The kind of identity check to run through {@link Verification.verifyIdentity}.
2008
+ *
2009
+ * Each value selects a different check at the configured provider, and each
2010
+ * expects a different payload alongside it - a cross-check of supplied details,
2011
+ * a date-of-birth confirmation, or a Social Security Number confirmation. The
2012
+ * required fields per check are in the API reference in your AsterMD dashboard.
2013
+ *
2014
+ * These calls carry PII, and `SsnVerify` in particular. The SDK never writes
2015
+ * their request bodies to a debug log.
2016
+ */
2017
+ declare const IdentityCheck: Readonly<{
2018
+ /** Cross-check the supplied identity details against the provider's records. */
2019
+ readonly Crosscheck: "crosscheck";
2020
+ /** Confirm the supplied date of birth. */
2021
+ readonly DobVerify: "dob_verify";
2022
+ /** Confirm the supplied Social Security Number. */
2023
+ readonly SsnVerify: "ssn_verify";
2024
+ }>;
2025
+ /** Any identity-check wire value. A bare string literal satisfies this type. */
2026
+ type IdentityCheck = (typeof IdentityCheck)[keyof typeof IdentityCheck];
2027
+
2028
+ /**
2029
+ * Verification - checking that the details a prospect typed are real.
2030
+ *
2031
+ * Telehealth needs more confidence in an address, an email, and an identity than
2032
+ * ordinary commerce does: a prescription goes to a physical person at a physical
2033
+ * place. These endpoints proxy the provider configured for your organisation, and
2034
+ * normalise its answers, so your storefront gets one shape regardless of which
2035
+ * provider sits behind it.
2036
+ *
2037
+ * Address autofill and verification belong at the point of entry - autofill as
2038
+ * the buyer types, verification before checkout completes. Email verification
2039
+ * catches typos and disposable addresses before they cost you a lost patient.
2040
+ * Identity verification is the strongest check and is usually required before a
2041
+ * clinician will review a case; which variant applies is carried by
2042
+ * {@link IdentityCheck}.
2043
+ *
2044
+ * Note where these calls appear in debug logs. Redaction covers headers and
2045
+ * bodies but never the URL, and the address and email endpoints take their input
2046
+ * as a query parameter - so a debug entry for those calls contains the address or
2047
+ * email that was checked. That is worth knowing when deciding where debug logs
2048
+ * are written and how long they are kept. Identity verification is the exception:
2049
+ * it is a POST, and its body is dropped from logs entirely because it carries
2050
+ * PII.
2051
+ */
2052
+ declare class Verification extends AbstractResource {
2053
+ /**
2054
+ * @param transport The shared transport.
2055
+ */
2056
+ constructor(transport: Transport);
2057
+ /**
2058
+ * Suggests complete addresses for a partial one.
2059
+ *
2060
+ * Built for a type-ahead: call it as the buyer types and render the
2061
+ * suggestions. Debouncing is your side's job - every keystroke here is a round
2062
+ * trip and counts against your request allowance.
2063
+ *
2064
+ * @param search The partial address typed so far.
2065
+ * @returns Candidate addresses from the configured provider.
2066
+ * @throws {ValidationError} If the search term is rejected.
2067
+ * @throws {RateLimitError} If the request allowance is exceeded.
2068
+ * @throws {ApiError} On any other non-2xx status.
2069
+ * @throws {TransportError} If the request never completed.
2070
+ */
2071
+ autofillAddress<T = Record<string, unknown>>(search: string): Promise<Response$1<T>>;
2072
+ /**
2073
+ * Verifies that an address exists and is deliverable.
2074
+ *
2075
+ * Call this before checkout completes rather than after: a prescription sent to
2076
+ * an undeliverable address is a clinical problem, not just a logistics one. The
2077
+ * response carries the provider's normalised form of the address, which is what
2078
+ * you should store.
2079
+ *
2080
+ * @param address The full address to verify.
2081
+ * @returns The verification result and the normalised address.
2082
+ * @throws {ValidationError} If the address is rejected.
2083
+ * @throws {ApiError} On any other non-2xx status.
2084
+ * @throws {TransportError} If the request never completed.
2085
+ */
2086
+ verifyAddress<T = Record<string, unknown>>(address: string): Promise<Response$1<T>>;
2087
+ /**
2088
+ * Verifies that an email address is real and reachable.
2089
+ *
2090
+ * Catches typos and disposable addresses at the point of entry. Since every
2091
+ * later notification - intake reminders, clinician messages, shipping updates -
2092
+ * goes to this address, a bad one silently costs you the patient.
2093
+ *
2094
+ * @param email The email address to verify.
2095
+ * @returns The verification result.
2096
+ * @throws {ValidationError} If the address is malformed.
2097
+ * @throws {ApiError} On any other non-2xx status.
2098
+ * @throws {TransportError} If the request never completed.
2099
+ */
2100
+ verifyEmail<T = Record<string, unknown>>(email: string): Promise<Response$1<T>>;
2101
+ /**
2102
+ * Runs an identity check against the configured provider.
2103
+ *
2104
+ * The strongest check available, and usually a precondition for clinical
2105
+ * review. Which variant runs is carried by `check`, and each expects a
2106
+ * different payload - see the API reference in your AsterMD dashboard for the
2107
+ * fields each one needs.
2108
+ *
2109
+ * The payload carries PII, and a Social Security Number in the `SsnVerify`
2110
+ * case. It is never written to a debug log, and it should not be written to
2111
+ * yours. The payload cannot override the check you selected.
2112
+ *
2113
+ * @param check Which identity check to run.
2114
+ * @param data The fields that check requires.
2115
+ * @returns The verification result.
2116
+ * @throws {ValidationError} If the payload does not satisfy the chosen check.
2117
+ * @throws {ApiError} On any other non-2xx status.
2118
+ * @throws {TransportError} If the request never completed.
2119
+ */
2120
+ verifyIdentity<T = Record<string, unknown>>(check: IdentityCheck, data: Record<string, unknown>): Promise<Response$1<T>>;
2121
+ }
2122
+
2123
+ /** Options accepted by {@link AsterMDClient}. */
2124
+ interface AsterMDClientOptions {
2125
+ /** OAuth2 client ID issued from your AsterMD dashboard. */
2126
+ clientId: string;
2127
+ /**
2128
+ * OAuth2 client secret. Load it from an environment variable or a secrets
2129
+ * manager - never commit it and never ship it to a browser.
2130
+ */
2131
+ clientSecret: string;
2132
+ /** Bare API hostname, no scheme and no path. Defaults to `api.astermd.com`. */
2133
+ baseHost?: string;
2134
+ /** Client used for every request. Defaults to {@link FetchHttpClient}. */
2135
+ httpClient?: HttpClient;
2136
+ /** Where the bearer token is cached. Defaults to {@link InMemoryTokenStore}. */
2137
+ tokenStore?: TokenStore;
2138
+ /** Per-request timeout in seconds. Must be >= 1. Defaults to 10. */
2139
+ timeoutSeconds?: number;
2140
+ /**
2141
+ * Turn on debug logging of every request and response. Requires `debugFile` or
2142
+ * `debugSink`. Defaults to `false`.
2143
+ */
2144
+ debug?: boolean;
2145
+ /**
2146
+ * Base path for daily log files, e.g. `/var/log/astermd/sdk.log`. Dated
2147
+ * filenames are derived from it. Ignored when `debugSink` is given.
2148
+ */
2149
+ debugFile?: string;
2150
+ /**
2151
+ * Receives each formatted entry instead of a file. Supplying this makes
2152
+ * retention and delivery yours.
2153
+ */
2154
+ debugSink?: DebugSink;
2155
+ /** IANA time zone for log timestamps and daily filenames. Defaults to `UTC`. */
2156
+ debugTimezone?: string;
2157
+ /**
2158
+ * Mask credentials and PHI in debug output. Defaults to `true`. Setting it
2159
+ * `false` writes live bearer tokens and the client secret to your sink - never
2160
+ * do that in production.
2161
+ */
2162
+ debugRedact?: boolean;
2163
+ /** Days of log files to keep; `0` keeps everything. Defaults to 7. */
2164
+ debugRetentionDays?: number;
2165
+ }
2166
+ /**
2167
+ * The SDK's entry point: one client, seventeen resources.
2168
+ *
2169
+ * Construct it once at application start and share it. It holds the credentials,
2170
+ * caches the bearer token, and owns the single transport every resource dispatches
2171
+ * through, so creating one per request would throw away the token cache and cost a
2172
+ * credential exchange each time.
2173
+ *
2174
+ * ```ts
2175
+ * import { AsterMDClient, Event } from '@astermd-hq/sdk';
2176
+ *
2177
+ * const client = new AsterMDClient({
2178
+ * clientId: process.env.ASTERMD_CLIENT_ID!,
2179
+ * clientSecret: process.env.ASTERMD_CLIENT_SECRET!,
2180
+ * });
2181
+ *
2182
+ * const { session } = (
2183
+ * await client.sessions().create<{ session: string }>()
2184
+ * ).data();
2185
+ *
2186
+ * await client.intakeSubmissions().create({
2187
+ * session,
2188
+ * event: Event.PreQualifyingInitiated,
2189
+ * teleformId: 'your-teleform-id',
2190
+ * data: answers,
2191
+ * });
2192
+ * ```
2193
+ *
2194
+ * Credentials are issued from your AsterMD dashboard, which is also where the
2195
+ * full API reference lives. This client holds a secret, so it belongs on a
2196
+ * server: never construct it in code that reaches a browser.
2197
+ *
2198
+ * Authentication needs no attention from you. The token is acquired lazily on the
2199
+ * first call, reused until it is close to expiring, refreshed automatically if the
2200
+ * server rejects it, and the organisation it authorises is encoded inside it -
2201
+ * which is why no method here takes an organisation argument.
2202
+ *
2203
+ * Every accessor is memoized, so `client.sessions()` returns the same instance
2204
+ * each time and calling it in a hot path costs nothing.
2205
+ */
2206
+ declare class AsterMDClient {
2207
+ #private;
2208
+ /**
2209
+ * @param options Credentials, connection settings, and debug configuration.
2210
+ * @throws {TypeError} If the credentials are empty, if `baseHost` carries a
2211
+ * scheme or path, if `timeoutSeconds` is below 1, or if `debug` is on with
2212
+ * neither `debugFile` nor `debugSink`.
2213
+ */
2214
+ constructor(options: AsterMDClientOptions);
2215
+ /**
2216
+ * Returns the immutable configuration this client was built with.
2217
+ *
2218
+ * @returns The configuration, including {@link Config.assetUrl}.
2219
+ */
2220
+ config(): Config;
2221
+ /**
2222
+ * Resolves a relative asset path returned by the API against the AsterMD CDN.
2223
+ *
2224
+ * A convenience for `client.config().assetUrl(path)`. Treat the result as a
2225
+ * fetch URL: download the asset once and serve your own copy rather than
2226
+ * hot-linking.
2227
+ *
2228
+ * @param path Relative asset path as returned by the API.
2229
+ * @returns The fully-qualified CDN URL.
2230
+ */
2231
+ assetUrl(path: string): string;
2232
+ /** Sessions - the thread tying one visitor's journey together. */
2233
+ sessions(): Sessions;
2234
+ /** Intake submissions - pre-qualifying and intake questionnaire answers. */
2235
+ intakeSubmissions(): IntakeSubmissions;
2236
+ /** Carts - what the visitor intends to buy. */
2237
+ carts(): Carts;
2238
+ /** Checkout events - progress through the checkout funnel. */
2239
+ checkoutEvents(): CheckoutEvents;
2240
+ /** Teleforms - the questionnaire definitions your storefront renders. */
2241
+ teleforms(): Teleforms;
2242
+ /** Patients - the person receiving care. Carries PHI. */
2243
+ patients(): Patients;
2244
+ /** Opportunities - a named prospect worth following up. */
2245
+ opportunities(): Opportunities;
2246
+ /** Treatments - the order, once payment has settled. */
2247
+ treatments(): Treatments;
2248
+ /** Doctors' networks - handing a case to the clinicians who review it. */
2249
+ doctorsNetworks(): DoctorsNetworks;
2250
+ /** Channels - the storefront's own server-side configuration. */
2251
+ channels(): Channels;
2252
+ /** Products - the read-only catalogue. */
2253
+ products(): Products;
2254
+ /** Categories - how the catalogue is grouped for browsing. */
2255
+ categories(): Categories;
2256
+ /** Lab tests - diagnostic panels a product may require. */
2257
+ labTests(): LabTests;
2258
+ /** Medications - the reference list backing "what are you currently taking?". */
2259
+ medications(): Medications;
2260
+ /** Shipping options - delivery methods available for an order. */
2261
+ shippings(): Shippings;
2262
+ /** Verification - address, email, and identity checks. */
2263
+ verification(): Verification;
2264
+ /** Geo - where a visitor is, and whether you may serve them. */
2265
+ geo(): Geo;
2266
+ }
2267
+
2268
+ /**
2269
+ * Base class for every error the SDK raises.
2270
+ *
2271
+ * Catching `AsterMDError` catches everything this SDK can throw and nothing it
2272
+ * cannot, which makes it the right thing to guard an integration boundary with.
2273
+ * Two branches descend from it: {@link TransportError} for failures where no HTTP
2274
+ * response was ever received, and {@link ApiError} for everything the server
2275
+ * answered with a non-2xx status.
2276
+ *
2277
+ * The class is abstract - the SDK never throws it directly.
2278
+ */
2279
+ declare abstract class AsterMDError extends Error {
2280
+ protected constructor(message: string, options?: ErrorOptions);
2281
+ }
2282
+
2283
+ /**
2284
+ * The API answered with a non-2xx status.
2285
+ *
2286
+ * This is the base for every status-carrying error and the class the SDK throws
2287
+ * for any status it does not map to something more specific - a 400, a 500, a
2288
+ * 502 from an upstream hiccup. The decoded response envelope is kept intact on
2289
+ * {@link ApiError.envelope} so an integrator can inspect whatever the server
2290
+ * chose to send alongside the status.
2291
+ *
2292
+ * Four subclasses cover the statuses worth branching on explicitly:
2293
+ * {@link AuthenticationError} (401), {@link NotFoundError} (404),
2294
+ * {@link ValidationError} (422), and {@link RateLimitError} (429).
2295
+ */
2296
+ declare class ApiError extends AsterMDError {
2297
+ #private;
2298
+ /**
2299
+ * @param message Human-readable message, taken from the envelope's `message`
2300
+ * field when the server sent one, otherwise `HTTP {status}`.
2301
+ * @param statusCode The HTTP status code the server returned.
2302
+ * @param envelope The decoded response body. Copied and frozen, so a later
2303
+ * mutation of the caller's object cannot change the error.
2304
+ */
2305
+ constructor(message: string, statusCode: number, envelope: Record<string, unknown>);
2306
+ /**
2307
+ * Returns the HTTP status code that caused this error.
2308
+ *
2309
+ * @returns The status code, e.g. `404` or `500`.
2310
+ */
2311
+ statusCode(): number;
2312
+ /**
2313
+ * Returns the decoded response envelope as the server sent it.
2314
+ *
2315
+ * Useful for surfacing a server-supplied message or an error code the SDK does
2316
+ * not model. The object is frozen; treat it as read-only.
2317
+ *
2318
+ * @returns The decoded body, or an empty object when the response had none.
2319
+ */
2320
+ envelope(): Readonly<Record<string, unknown>>;
2321
+ }
2322
+
2323
+ /**
2324
+ * The API rejected the credentials or the bearer token (HTTP 401).
2325
+ *
2326
+ * The SDK does not raise this on the first 401. {@link TokenManager} refreshes
2327
+ * the token and the request is replayed once; only a second 401 surfaces here.
2328
+ * So seeing this error means a fresh token was also refused - check the client ID
2329
+ * and secret, and that the credentials are still active in your AsterMD
2330
+ * dashboard.
2331
+ */
2332
+ declare class AuthenticationError extends ApiError {
2333
+ constructor(message: string, statusCode: number, envelope: Record<string, unknown>);
2334
+ }
2335
+
2336
+ /**
2337
+ * The addressed resource does not exist (HTTP 404).
2338
+ *
2339
+ * Raised when an id in the request path matches nothing the calling organisation
2340
+ * can see. Note that the organisation scope lives inside the JWT, so an id
2341
+ * belonging to another organisation is indistinguishable from one that never
2342
+ * existed - both arrive here.
2343
+ */
2344
+ declare class NotFoundError extends ApiError {
2345
+ constructor(message: string, statusCode: number, envelope: Record<string, unknown>);
2346
+ }
2347
+
2348
+ /**
2349
+ * The calling organisation exceeded its request allowance (HTTP 429).
2350
+ *
2351
+ * When the server sent a `Retry-After` header, its value in seconds is available
2352
+ * on {@link RateLimitError.retryAfter} - wait at least that long before trying
2353
+ * again. The header is optional, so treat `null` as "back off on your own
2354
+ * schedule" rather than "retry immediately".
2355
+ */
2356
+ declare class RateLimitError extends ApiError {
2357
+ #private;
2358
+ /**
2359
+ * @param message Human-readable summary from the envelope.
2360
+ * @param statusCode Always 429 in practice.
2361
+ * @param envelope The decoded response body.
2362
+ * @param retryAfter Seconds to wait, from the `Retry-After` header, or `null`
2363
+ * when the header was absent or not a plain integer.
2364
+ */
2365
+ constructor(message: string, statusCode: number, envelope: Record<string, unknown>, retryAfter: number | null);
2366
+ /**
2367
+ * Returns the server-advised wait in seconds before retrying.
2368
+ *
2369
+ * @returns Seconds to wait, or `null` when the server did not say.
2370
+ */
2371
+ retryAfter(): number | null;
2372
+ }
2373
+
2374
+ /**
2375
+ * A request never reached the API, or its response never came back.
2376
+ *
2377
+ * Raised for DNS failures, TLS errors, connection resets, and timeouts - every
2378
+ * case where the SDK has no HTTP status to reason about. The originating error is
2379
+ * preserved on `cause`. There is no `statusCode()` here by design: nothing was
2380
+ * returned to have a status.
2381
+ *
2382
+ * These are usually worth retrying with backoff; an {@link ApiError} usually is
2383
+ * not.
2384
+ */
2385
+ declare class TransportError extends AsterMDError {
2386
+ constructor(message: string, options?: ErrorOptions);
2387
+ }
2388
+
2389
+ /**
2390
+ * The request was well-formed but the server refused its contents (HTTP 422).
2391
+ *
2392
+ * The interesting part is {@link ValidationError.fieldErrors}, which maps each
2393
+ * rejected field to the messages the server produced for it - exactly the shape
2394
+ * a form needs to render inline errors next to the offending inputs.
2395
+ */
2396
+ declare class ValidationError extends ApiError {
2397
+ #private;
2398
+ /**
2399
+ * @param message Human-readable summary from the envelope.
2400
+ * @param statusCode Always 422 in practice.
2401
+ * @param envelope The decoded response body.
2402
+ * @param fieldErrors Field name → list of messages, extracted from the
2403
+ * envelope's `errors` key by {@link Transport}.
2404
+ */
2405
+ constructor(message: string, statusCode: number, envelope: Record<string, unknown>, fieldErrors: Record<string, string[]>);
2406
+ /**
2407
+ * Returns the per-field validation messages the server produced.
2408
+ *
2409
+ * @returns Field name → messages. Empty when the server sent no `errors` key.
2410
+ */
2411
+ fieldErrors(): Readonly<Record<string, string[]>>;
2412
+ }
2413
+
2414
+ /**
2415
+ * The default {@link HttpClient}: the platform `fetch` plus a timeout.
2416
+ *
2417
+ * This is what {@link AsterMDClient} uses unless you inject something else. It
2418
+ * adds exactly two things to `fetch`. First, a deadline: every request carries an
2419
+ * `AbortSignal.timeout()` built from the configured `timeoutSeconds`, covering
2420
+ * connection and response together. Second, uniform failures: a DNS error, a
2421
+ * connection reset, a TLS failure, or a timeout all surface as
2422
+ * {@link TransportError} with the original error preserved on `cause`, so
2423
+ * integrators can catch one type instead of guessing at platform error shapes.
2424
+ *
2425
+ * It holds no connection state, so one instance is safely shared across
2426
+ * concurrent requests.
2427
+ */
2428
+ declare class FetchHttpClient implements HttpClient {
2429
+ #private;
2430
+ /**
2431
+ * @param timeoutSeconds Per-request deadline in seconds. Must be >= 1.
2432
+ * @param fetchImpl The `fetch` implementation to call. Defaults to the global
2433
+ * one; override it to supply an instrumented or stubbed
2434
+ * `fetch` without implementing the whole interface.
2435
+ * @throws {TypeError} If `timeoutSeconds` is below 1.
2436
+ */
2437
+ constructor(timeoutSeconds?: number, fetchImpl?: typeof fetch);
2438
+ /**
2439
+ * Sends the request through `fetch` under the configured deadline.
2440
+ *
2441
+ * @param request The request to send.
2442
+ * @returns The response as received, whatever its status.
2443
+ * @throws {TransportError} If the request aborts on the deadline or fails at
2444
+ * the network layer.
2445
+ */
2446
+ sendRequest(request: Request): Promise<Response>;
2447
+ }
2448
+
2449
+ /**
2450
+ * The default {@link TokenStore}: keeps the token in memory for the life of the
2451
+ * process.
2452
+ *
2453
+ * Right for a long-running server - an Express or Fastify app, a worker - where
2454
+ * one credential exchange serves every request until the token expires. Nothing
2455
+ * is written anywhere, so nothing can leak to disk.
2456
+ *
2457
+ * Wrong where each request runs in a fresh process or isolate, since the cache
2458
+ * dies with it and every request pays for its own token exchange. Use
2459
+ * {@link FileTokenStore} or your own {@link TokenStore} there.
2460
+ */
2461
+ declare class InMemoryTokenStore implements TokenStore {
2462
+ #private;
2463
+ /**
2464
+ * Returns the token held in memory, or `null` before the first exchange.
2465
+ *
2466
+ * @returns The cached token, or `null`.
2467
+ */
2468
+ get(): Promise<Token | null>;
2469
+ /**
2470
+ * Replaces the token held in memory.
2471
+ *
2472
+ * @param token The token to cache.
2473
+ */
2474
+ put(token: Token): Promise<void>;
2475
+ /**
2476
+ * Drops the token held in memory.
2477
+ */
2478
+ clear(): Promise<void>;
2479
+ }
2480
+
2481
+ /**
2482
+ * File-backed {@link TokenStore} that persists the JWT across processes.
2483
+ *
2484
+ * Unlike {@link InMemoryTokenStore}, this writes the token as JSON to disk so it
2485
+ * survives a process boundary. That makes it the right choice where each request
2486
+ * or invocation gets a fresh process and an in-memory cache would never be hit,
2487
+ * as long as those processes share a filesystem.
2488
+ *
2489
+ * ```ts
2490
+ * const client = new AsterMDClient({
2491
+ * clientId: process.env.ASTERMD_CLIENT_ID!,
2492
+ * clientSecret: process.env.ASTERMD_CLIENT_SECRET!,
2493
+ * tokenStore: new FileTokenStore('/var/cache/astermd/token.json'),
2494
+ * });
2495
+ * ```
2496
+ *
2497
+ * The parent directory is created if absent. The file is written `0o600` - owner
2498
+ * read and write only - so the token is not world-readable on a shared host. It
2499
+ * must be writable by the service account and should sit outside any document
2500
+ * root. One file per set of API credentials is enough.
2501
+ *
2502
+ * Writes are atomic: the content goes to a uniquely named temporary file in the
2503
+ * same directory and is then renamed over the target, so a concurrent reader
2504
+ * sees either the whole previous token or the whole new one. Both reads and
2505
+ * writes treat any failure - missing file, bad permissions, a read-only cache
2506
+ * directory, truncated or corrupt JSON - as "no token" rather than raising,
2507
+ * which makes a damaged cache cost one extra credential exchange instead of
2508
+ * breaking the request it was only meant to optimise.
2509
+ */
2510
+ declare class FileTokenStore implements TokenStore {
2511
+ #private;
2512
+ /**
2513
+ * @param path Path to the JSON file used for token storage. The parent
2514
+ * directory is created on first write if it does not exist.
2515
+ */
2516
+ constructor(path: string);
2517
+ /**
2518
+ * Reads the cached token from the file.
2519
+ *
2520
+ * @returns The stored token, or `null` when the file is absent, unreadable, or
2521
+ * does not hold a usable token.
2522
+ */
2523
+ get(): Promise<Token | null>;
2524
+ /**
2525
+ * Writes the token to the backing file, replacing any existing content.
2526
+ *
2527
+ * Persistence is best-effort: a filesystem failure - a read-only or
2528
+ * misconfigured cache directory, a permissions problem - is swallowed rather
2529
+ * than thrown, matching the PHP SDK's `put()`, which returns early on a
2530
+ * `fopen` failure. This method runs on every token acquisition, so failing it
2531
+ * would fail the API call it was only meant to optimise; the cost of a
2532
+ * degraded cache is an extra credential exchange next time, not a broken
2533
+ * request.
2534
+ *
2535
+ * @param token The freshly acquired token to persist.
2536
+ */
2537
+ put(token: Token): Promise<void>;
2538
+ /**
2539
+ * Deletes the backing file so the next {@link FileTokenStore.get} returns
2540
+ * `null`.
2541
+ *
2542
+ * A no-op when the file does not exist, including when another process
2543
+ * removed it first. Best-effort otherwise: any other filesystem failure (for
2544
+ * example a permissions problem) is swallowed rather than thrown, since a
2545
+ * cache that fails to clear only costs a stale-token read on the next call,
2546
+ * which {@link FileTokenStore.get} already tolerates.
2547
+ */
2548
+ clear(): Promise<void>;
2549
+ }
2550
+
2551
+ /** Options accepted by {@link DailyFileLogSink}. */
2552
+ interface DailyFileLogSinkOptions {
2553
+ /**
2554
+ * Undated path the daily filenames are derived from, e.g.
2555
+ * `/var/log/astermd/sdk.log`. A path with no extension gets `.log`.
2556
+ */
2557
+ basePath: string;
2558
+ /**
2559
+ * Days of logs to keep. A file is removed once its date is more than this many
2560
+ * days before today. Use `0` to keep every file forever. Defaults to 7.
2561
+ */
2562
+ retentionDays?: number;
2563
+ /**
2564
+ * IANA time zone deciding which calendar day an entry belongs to. Pass the same
2565
+ * zone used for log timestamps so filenames and their contents agree. Defaults
2566
+ * to `UTC`.
2567
+ */
2568
+ timeZone?: string;
2569
+ }
2570
+ /**
2571
+ * Debug-log sink that writes one file per calendar day and prunes old files.
2572
+ *
2573
+ * The debug logger emits one entry per HTTP request. Appending those to a single
2574
+ * file grows without bound, which is why this sink dates each file and deletes
2575
+ * the ones that have aged out - the same arrangement as conventional daily log
2576
+ * rotation, but self-contained, so no external rotation tooling is needed.
2577
+ *
2578
+ * You give it a *base path* and it derives dated filenames from it, keeping the
2579
+ * directory, stem, and extension you chose:
2580
+ *
2581
+ * ```
2582
+ * basePath: /var/log/astermd/sdk.log
2583
+ *
2584
+ * /var/log/astermd/sdk-2026-09-08.log ← today, appended to
2585
+ * /var/log/astermd/sdk-2026-09-07.log
2586
+ * /var/log/astermd/sdk-2026-09-01.log ← deleted once it is over 7 days old
2587
+ * ```
2588
+ *
2589
+ * {@link AsterMDClient} builds one for you when you pass `debugFile`; construct
2590
+ * it directly only if you want to reuse one sink across several clients.
2591
+ *
2592
+ * Pruning reads the date out of each filename rather than the filesystem's
2593
+ * modification time, so a file that was merely touched is still removed on
2594
+ * schedule and unrelated files sharing the directory are never matched. It runs
2595
+ * at most once per instance, on the first write, which keeps it off the hot path.
2596
+ *
2597
+ * Writes are serialized through an internal promise chain, so concurrent requests
2598
+ * cannot interleave halves of two entries. Every I/O failure is swallowed: losing
2599
+ * a debug entry is never worth failing the request that produced it.
2600
+ *
2601
+ * Supplying your own `debugSink` bypasses this class entirely, and retention then
2602
+ * becomes that system's responsibility.
2603
+ */
2604
+ declare class DailyFileLogSink {
2605
+ #private;
2606
+ /**
2607
+ * @param options Base path, retention window, and time zone.
2608
+ * @throws {TypeError} If `basePath` is empty or carries no file name, or if
2609
+ * `retentionDays` is negative.
2610
+ */
2611
+ constructor(options: DailyFileLogSinkOptions);
2612
+ /**
2613
+ * Appends one entry to today's log file, pruning aged-out files on the first
2614
+ * call.
2615
+ *
2616
+ * Resolves once the entry is on disk. Writes are chained, so entries land whole
2617
+ * and in call order even under concurrency. Never rejects: an I/O failure is
2618
+ * swallowed rather than propagated into the API call being logged.
2619
+ *
2620
+ * @param entry The formatted log entry to append.
2621
+ */
2622
+ write(entry: string): Promise<void>;
2623
+ /**
2624
+ * Returns the absolute path entries are currently written to.
2625
+ *
2626
+ * Useful in tests and for a "where are my logs" diagnostic. The path changes at
2627
+ * midnight in the configured time zone.
2628
+ *
2629
+ * @returns The dated file path, e.g. `/var/log/astermd/sdk-2026-09-08.log`.
2630
+ */
2631
+ currentFile(): string;
2632
+ }
2633
+
2634
+ /** One integration's mapping for a variant or a single-price product. */
2635
+ interface ChannelProductMapping {
2636
+ /** The integration's type, from the mapping's `integration.type`. */
2637
+ type: unknown;
2638
+ /** Every other key of the raw `mapping` object, verbatim. */
2639
+ [key: string]: unknown;
2640
+ }
2641
+ /** A purchasable variant of a product, with its price and integration mapping. */
2642
+ interface ChannelProductVariant {
2643
+ id: unknown;
2644
+ name: unknown;
2645
+ sku: unknown;
2646
+ price: unknown;
2647
+ intro_price: unknown;
2648
+ sale_price: unknown;
2649
+ sale_start: unknown;
2650
+ sale_end: unknown;
2651
+ mapping: ChannelProductMapping | null;
2652
+ }
2653
+ /** Single-purchase pricing for a product that has no variants. */
2654
+ interface ChannelProductSingle {
2655
+ price: unknown;
2656
+ intro_price: unknown;
2657
+ sale_price: unknown;
2658
+ sale_start: unknown;
2659
+ sale_end: unknown;
2660
+ mapping: ChannelProductMapping | null;
2661
+ }
2662
+ /** A product as the channel offers it. */
2663
+ interface ChannelProduct {
2664
+ id: unknown;
2665
+ name: unknown;
2666
+ type: unknown;
2667
+ sku: unknown;
2668
+ status: unknown;
2669
+ visibility: unknown;
2670
+ image: unknown;
2671
+ description_long: unknown;
2672
+ description_short: unknown;
2673
+ restrict_multiple: unknown;
2674
+ min_buy_qty: unknown;
2675
+ max_buy_qty: unknown;
2676
+ stock: unknown;
2677
+ categories: unknown[];
2678
+ condition_treated: unknown[];
2679
+ teleforms: unknown;
2680
+ labtest: unknown[];
2681
+ add_ons: ChannelProduct[];
2682
+ single: ChannelProductSingle | null;
2683
+ variants: ChannelProductVariant[];
2684
+ }
2685
+ /** The channel's payment processor configuration. */
2686
+ interface ChannelPaymentProcessor {
2687
+ id: unknown;
2688
+ name: unknown;
2689
+ provider: unknown;
2690
+ type: unknown;
2691
+ config: unknown;
2692
+ }
2693
+ /** The flattened channel-detail envelope. */
2694
+ interface ChannelDetailView {
2695
+ id: unknown;
2696
+ name: unknown;
2697
+ description: unknown;
2698
+ status: unknown;
2699
+ type: unknown;
2700
+ auto_inc_id: unknown;
2701
+ payment_processor: ChannelPaymentProcessor | null;
2702
+ products: ChannelProduct[];
2703
+ }
2704
+ /**
2705
+ * Read model over the channel-detail envelope.
2706
+ *
2707
+ * {@link Channels.details} returns everything a storefront needs in one
2708
+ * response, which makes it efficient and awkward in equal measure: the payload is
2709
+ * deeply nested, uses the server's own field names, and scatters each variant's
2710
+ * integration mapping into a separate parallel list keyed by variant id. Rendering
2711
+ * straight from it means writing that reassembly into your view layer.
2712
+ *
2713
+ * This presenter does the reassembly once. It renames `_id` to `id` throughout,
2714
+ * joins each variant to its mapping, hoists single-purchase pricing out of its
2715
+ * one-element list, flattens reference objects, and recurses through add-ons so
2716
+ * they look like ordinary products:
2717
+ *
2718
+ * ```ts
2719
+ * const detail = ChannelDetail.from((await client.channels().details(channelId)).data());
2720
+ *
2721
+ * for (const product of detail.products) {
2722
+ * for (const variant of product.variants) {
2723
+ * render(product.name, variant.name, variant.price, variant.mapping);
2724
+ * }
2725
+ * }
2726
+ * ```
2727
+ *
2728
+ * Field names in the output stay snake_case, matching the PHP SDK's presenter, so
2729
+ * a value produced by one can be compared against the other. Anything the server
2730
+ * omitted comes back as `null` rather than `undefined`, and an omitted list comes
2731
+ * back empty - so a view can read straight through without optional chaining at
2732
+ * every level.
2733
+ *
2734
+ * The class is not instantiable; it exposes only {@link ChannelDetail.from}.
2735
+ */
2736
+ declare class ChannelDetail {
2737
+ private constructor();
2738
+ /**
2739
+ * Flattens a channel-detail payload into a shape a view can render directly.
2740
+ *
2741
+ * Pass `response.data()` from {@link Channels.details}. Malformed or missing
2742
+ * branches degrade to `null` and `[]` rather than raising, because a channel is
2743
+ * configuration a storefront needs at boot: failing the page over one absent
2744
+ * optional field would be the wrong trade.
2745
+ *
2746
+ * @param data The raw `data` object from the channel-detail response.
2747
+ * @returns The flattened view.
2748
+ */
2749
+ static from(data: Record<string, unknown>): ChannelDetailView;
2750
+ }
2751
+
2752
+ /**
2753
+ * Encrypts and decrypts URL query-parameter tokens using AES-128-CBC, with a
2754
+ * random 16-byte IV prepended to the ciphertext and the whole thing
2755
+ * base64url-encoded.
2756
+ *
2757
+ * Tracking parameters are encrypted so a browser extension cannot read them in
2758
+ * transit. The codec is parameter-agnostic: it carries no knowledge of what it
2759
+ * is transporting.
2760
+ *
2761
+ * Decryption is deliberately total. A malformed, truncated, tampered, or
2762
+ * wrongly-keyed token yields an empty object rather than raising, so a storefront
2763
+ * can fall back to plain parameters instead of failing a page load:
2764
+ *
2765
+ * ```ts
2766
+ * const params = QueryParamCipher.decrypt(
2767
+ * request.query._amd ?? '',
2768
+ * process.env.ASTERMD_PARAM_KEY!,
2769
+ * );
2770
+ * // => { aff_id: '123', utm_source: 'news' } (or {} on any failure)
2771
+ * ```
2772
+ *
2773
+ * The AES key is **never** stored in this SDK. Supply it at every call site from
2774
+ * your own configuration - an environment variable, a secrets manager - and keep
2775
+ * it in step with whichever service mints the tokens. Contact info@astermd.com to
2776
+ * obtain or rotate the key for your organisation.
2777
+ *
2778
+ * **On integrity:** AES-CBC provides confidentiality but not authentication, so a
2779
+ * token cannot be proven unmodified. Treat decrypted parameters as untrusted
2780
+ * input and validate them before use.
2781
+ *
2782
+ * **On the wire format and the PHP SDK:** the encrypted payload is a JSON *list*
2783
+ * of `{ key, value }` objects, which is what the PHP SDK writes and reads, so a
2784
+ * token minted by either SDK is readable by the other. This class's own surface
2785
+ * differs from the PHP one, though: it takes and returns a plain object, since
2786
+ * that is what a JS caller wants, and converts at the boundary. A duplicate key
2787
+ * on the wire resolves to its last occurrence.
2788
+ */
2789
+ declare class QueryParamCipher {
2790
+ private constructor();
2791
+ /**
2792
+ * Decrypts a query-parameter token into tracking parameters.
2793
+ *
2794
+ * Call this at your storefront's entry point with the raw token from the URL
2795
+ * and the key from your own configuration. Any failure along the way - bad
2796
+ * encoding, wrong or malformed key, decryption error, non-list payload -
2797
+ * returns an empty object so the caller can fall back to plain parameters.
2798
+ *
2799
+ * @param token The base64url token, `iv(16) || ciphertext`.
2800
+ * @param keyHex 32 hex characters - a 16-byte AES-128 key. Supplied by you;
2801
+ * the SDK ships no default.
2802
+ * @returns The decrypted parameters, or `{}` on any failure.
2803
+ */
2804
+ static decrypt(token: string, keyHex: string): Record<string, string>;
2805
+ /**
2806
+ * Encrypts tracking parameters into a token.
2807
+ *
2808
+ * Provided mainly for round-trip tests and local tooling. A fresh random IV is
2809
+ * generated on every call and prepended to the ciphertext before encoding, so
2810
+ * the same parameters never produce the same token twice.
2811
+ *
2812
+ * Unlike {@link QueryParamCipher.decrypt}, this is strict: a bad key raises
2813
+ * rather than being swallowed, because a failed encrypt is a programming error
2814
+ * and not untrusted input.
2815
+ *
2816
+ * @param params The parameters to encrypt. Written to the wire as the
2817
+ * `[{ key, value }, …]` list the PHP SDK also reads.
2818
+ * @param keyHex 32 hex characters - a 16-byte AES-128 key.
2819
+ * @returns The base64url token, `iv(16) || ciphertext`.
2820
+ * @throws {TypeError} If `keyHex` is not a valid 16-byte hex key.
2821
+ */
2822
+ static encrypt(params: Record<string, string>, keyHex: string): string;
2823
+ }
2824
+
2825
+ export { AbstractResource, ApiError, AsterMDClient, type AsterMDClientOptions, AsterMDError, AuthenticationError, type CartWriteOptions, Carts, Categories, ChannelDetail, type ChannelDetailView, type ChannelPaymentProcessor, type ChannelProduct, type ChannelProductMapping, type ChannelProductSingle, type ChannelProductVariant, Channels, CheckoutEvent, type CheckoutEventUpdateOptions, CheckoutEvents, Config, type ConfigOptions, DailyFileLogSink, type DailyFileLogSinkOptions, type DebugSink, DoctorsNetworks, Event, FetchHttpClient, FileTokenStore, FileUpload, type FileUploadFromContentsOptions, type FileUploadFromPathOptions, type FinishMultipartOptions, Geo, type HttpClient, IdentityCheck, InMemoryTokenStore, type InitiateMultipartOptions, IntakeSubmissions, type IntakeViewOptions, type IntakeWriteOptions, LabTests, Medications, type MultipartPart, NotFoundError, Opportunities, Patients, Products, QueryParamCipher, RateLimitError, Response$1 as Response, type ResponseOptions, type SessionCreateOptions, type SessionViewOptions, Sessions, Shippings, type SubmitHealthInformationOptions, Teleforms, Token, type TokenStore, TransportError, type TreatmentSyncOptions, Treatments, type UploadFileOptions, type UploadLargeFileOptions, type UploadPartOptions, ValidationError, Verification };