// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types'; import type { HTTPMethod, PromiseOrValue, MergedRequestInit, FinalizedRequestInit } from './internal/types'; import { uuid4 } from './internal/utils/uuid'; import { validatePositiveInteger, isAbsoluteURL, safeJSON } from './internal/utils/values'; import { sleep } from './internal/utils/sleep'; export type { Logger, LogLevel } from './internal/utils/log'; import { castToError, isAbortError } from './internal/errors'; import type { APIResponseProps } from './internal/parse'; import { getPlatformHeaders } from './internal/detect-platform'; import * as Shims from './internal/shims'; import * as Opts from './internal/request-options'; import { stringifyQuery } from './internal/utils/query'; import { VERSION } from './version'; import * as Errors from './core/error'; import * as Pagination from './core/pagination'; import { AbstractPage, type DefaultPaginationParams, DefaultPaginationResponse } from './core/pagination'; import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; import { BeneficialOwnerCreateParams, BeneficialOwnerListParams, BeneficialOwnerPersonalInfo, BeneficialOwnerUpdateParams, BeneficialOwners, } from './resources/beneficial-owners'; import { CardIssueParams, CardIssueResponse, CardListParams, CardListResponse, CardListResponsesDefaultPagination, CardRetrieveResponse, CardUpdateParams, CardUpdateResponse, Cards, } from './resources/cards'; import { Config, ConfigUpdateParams, CustomerInfoFieldName, EmbeddedWalletConfig, PlatformConfig, PlatformCurrencyConfig, } from './resources/config'; import { Crypto, CryptoEstimateWithdrawalFeeParams, CryptoEstimateWithdrawalFeeResponse, } from './resources/crypto'; import { Discoveries, DiscoveryListParams, DiscoveryListResponse } from './resources/discoveries'; import { DocumentListParams, DocumentListResponse, DocumentListResponsesDefaultPagination, DocumentReplaceParams, DocumentReplaceResponse, DocumentRetrieveResponse, DocumentUploadParams, DocumentUploadResponse, Documents, } from './resources/documents'; import { ExchangeRateListParams, ExchangeRateListResponse, ExchangeRates } from './resources/exchange-rates'; import { CurrencyAmount, InvitationClaimParams, InvitationCreateParams, Invitations, UmaInvitation, } from './resources/invitations'; import { BaseDestination, BaseQuoteSource, Currency, OutgoingRateDetails, PaymentInstructions, Quote, QuoteCreateParams, QuoteDestinationOneOf, QuoteExecuteParams, QuoteSourceOneOf, Quotes, } from './resources/quotes'; import { CounterpartyFieldDefinition, LookupResponse, Receiver, ReceiverLookupExternalAccountParams, ReceiverLookupExternalAccountResponse, ReceiverLookupUmaParams, ReceiverLookupUmaResponse, } from './resources/receiver'; import { APIToken, APITokensDefaultPagination, Permission, TokenCreateParams, TokenListParams, Tokens, } from './resources/tokens'; import { BaseTransactionSource, IncomingRateDetails, IncomingTransaction, OutgoingTransaction, OutgoingTransactionStatus, ReconciliationInstructions, TransactionApproveParams, TransactionListParams, TransactionRejectParams, TransactionSourceOneOf, TransactionStatus, TransactionType, Transactions, } from './resources/transactions'; import { BaseTransactionDestination, ExternalAccountReference, InternalAccountReference, Transaction, TransferIn, TransferInCreateParams, } from './resources/transfer-in'; import { TransferOut, TransferOutCreateParams } from './resources/transfer-out'; import { UmaProviderListParams, UmaProviderListResponse, UmaProviderListResponsesDefaultPagination, UmaProviders, } from './resources/uma-providers'; import { VerificationListParams, VerificationListResponse, VerificationListResponsesDefaultPagination, VerificationRetrieveResponse, VerificationSubmitParams, VerificationSubmitResponse, Verifications, } from './resources/verifications'; import { AgentActionWebhookEvent, BulkUploadWebhookEvent, CardFundingSourceChangeWebhookEvent, CardStateChangeWebhookEvent, CustomerUpdateWebhookEvent, IncomingPaymentWebhookEvent, InternalAccountStatusWebhookEvent, InvitationClaimedWebhookEvent, OutgoingPaymentWebhookEvent, TestWebhookWebhookEvent, UnwrapWebhookEvent, VerificationUpdateWebhookEvent, Webhooks, } from './resources/webhooks'; import { Agent, AgentAccountRestrictions, AgentAccountRule, AgentAction, AgentActionListResponse, AgentActionRejectRequest, AgentActionsDefaultPagination, AgentApprovalThresholds, AgentCreateParams, AgentCreateRequest, AgentCreateResponse, AgentDeviceCode, AgentDeviceCodeRedeemResponse, AgentDeviceCodeStatusResponse, AgentListApprovalsParams, AgentListParams, AgentListResponse, AgentPolicy, AgentUpdateParams, AgentUpdatePolicyParams, AgentUpdateRequest, AgentUsage, Agents, AgentsDefaultPagination, } from './resources/agents/agents'; import { Auth } from './resources/auth/auth'; import { CustomerCreateParams, CustomerCreateResponse, CustomerDeleteResponse, CustomerExportParams, CustomerExportResponse, CustomerGenerateKYCLinkParams, CustomerGenerateKYCLinkResponse, CustomerListInternalAccountsParams, CustomerListParams, CustomerListResponse, CustomerListResponsesDefaultPagination, CustomerRetrieveResponse, CustomerUpdateInternalAccountParams, CustomerUpdateParams, CustomerUpdateResponse, Customers, } from './resources/customers/customers'; import { Platform, PlatformListInternalAccountsParams, PlatformListInternalAccountsResponse, } from './resources/platform/platform'; import { Sandbox, SandboxSendFundsParams } from './resources/sandbox/sandbox'; import { type Fetch } from './internal/builtin-types'; import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; import { FinalRequestOptions, RequestOptions } from './internal/request-options'; import { toBase64 } from './internal/utils/base64'; import { readEnv } from './internal/utils/env'; import { type LogLevel, type Logger, formatRequestDetails, loggerFor, parseLogLevel, } from './internal/utils/log'; import { isEmptyObj } from './internal/utils/values'; export interface ClientOptions { /** * API token authentication using format `:` */ username?: string | null | undefined; /** * API token authentication using format `:` */ password?: string | null | undefined; /** * Bearer access token obtained by redeeming a device code. Required when calling agent-scoped endpoints (e.g. `GET /agents/me/...`). Leave unset for platform-scoped operations. */ agentAccessToken?: string | null | undefined; /** * Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. * * To verify the signature: * 1. Get the Grid public key provided to you during integration * 2. Decode the base64 signature from the header * 3. Create a SHA-256 hash of the request body * 4. Verify the signature using the public key and the hash * * If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. * */ webhookSignature?: string | null | undefined; /** * Override the default base URL for the API, e.g., "https://api.example.com/v2/" * * Defaults to process.env['LIGHTSPARK_GRID_BASE_URL']. */ baseURL?: string | null | undefined; /** * The maximum amount of time (in milliseconds) that the client should wait for a response * from the server before timing out a single request. * * Note that request timeouts are retried by default, so in a worst-case scenario you may wait * much longer than this timeout before the promise succeeds or fails. * * @unit milliseconds */ timeout?: number | undefined; /** * Additional `RequestInit` options to be passed to `fetch` calls. * Properties will be overridden by per-request `fetchOptions`. */ fetchOptions?: MergedRequestInit | undefined; /** * Specify a custom `fetch` function implementation. * * If not provided, we expect that `fetch` is defined globally. */ fetch?: Fetch | undefined; /** * The maximum number of times that the client will retry a request in case of a * temporary failure, like a network error or a 5XX error from the server. * * @default 2 */ maxRetries?: number | undefined; /** * Default headers to include with every request to the API. * * These can be removed in individual requests by explicitly setting the * header to `null` in request options. */ defaultHeaders?: HeadersLike | undefined; /** * Default query parameters to include with every request to the API. * * These can be removed in individual requests by explicitly setting the * param to `undefined` in request options. */ defaultQuery?: Record | undefined; /** * Set the log level. * * Defaults to process.env['LIGHTSPARK_GRID_LOG'] or 'warn' if it isn't set. */ logLevel?: LogLevel | undefined; /** * Set the logger. * * Defaults to globalThis.console. */ logger?: Logger | undefined; } /** * API Client for interfacing with the Lightspark Grid API. */ export class LightsparkGrid { username: string | null; password: string | null; agentAccessToken: string | null; webhookSignature: string | null; baseURL: string; maxRetries: number; timeout: number; logger: Logger; logLevel: LogLevel | undefined; fetchOptions: MergedRequestInit | undefined; private fetch: Fetch; #encoder: Opts.RequestEncoder; protected idempotencyHeader?: string; private _options: ClientOptions; /** * API Client for interfacing with the Lightspark Grid API. * * @param {string | null | undefined} [opts.username=process.env['GRID_CLIENT_ID'] ?? null] * @param {string | null | undefined} [opts.password=process.env['GRID_CLIENT_SECRET'] ?? null] * @param {string | null | undefined} [opts.agentAccessToken=process.env['GRID_AGENT_ACCESS_TOKEN'] ?? null] * @param {string | null | undefined} [opts.webhookSignature=process.env['GRID_WEBHOOK_PUBKEY'] ?? null] * @param {string} [opts.baseURL=process.env['LIGHTSPARK_GRID_BASE_URL'] ?? https://api.lightspark.com/grid/2025-10-13] - Override the default base URL for the API. * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. */ constructor({ baseURL = readEnv('LIGHTSPARK_GRID_BASE_URL'), username = readEnv('GRID_CLIENT_ID') ?? null, password = readEnv('GRID_CLIENT_SECRET') ?? null, agentAccessToken = readEnv('GRID_AGENT_ACCESS_TOKEN') ?? null, webhookSignature = readEnv('GRID_WEBHOOK_PUBKEY') ?? null, ...opts }: ClientOptions = {}) { const options: ClientOptions = { username, password, agentAccessToken, webhookSignature, ...opts, baseURL: baseURL || `https://api.lightspark.com/grid/2025-10-13`, }; this.baseURL = options.baseURL!; this.timeout = options.timeout ?? LightsparkGrid.DEFAULT_TIMEOUT /* 1 minute */; this.logger = options.logger ?? console; const defaultLogLevel = 'warn'; // Set default logLevel early so that we can log a warning in parseLogLevel. this.logLevel = defaultLogLevel; this.logLevel = parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? parseLogLevel(readEnv('LIGHTSPARK_GRID_LOG'), "process.env['LIGHTSPARK_GRID_LOG']", this) ?? defaultLogLevel; this.fetchOptions = options.fetchOptions; this.maxRetries = options.maxRetries ?? 2; this.fetch = options.fetch ?? Shims.getDefaultFetch(); this.#encoder = Opts.FallbackEncoder; const customHeadersEnv = readEnv('LIGHTSPARK_GRID_CUSTOM_HEADERS'); if (customHeadersEnv) { const parsed: Record = {}; for (const line of customHeadersEnv.split('\n')) { const colon = line.indexOf(':'); if (colon >= 0) { parsed[line.substring(0, colon).trim()] = line.substring(colon + 1).trim(); } } options.defaultHeaders = { ...parsed, ...options.defaultHeaders }; } this._options = options; this.username = username; this.password = password; this.agentAccessToken = agentAccessToken; this.webhookSignature = webhookSignature; } /** * Create a new client instance re-using the same options given to the current client with optional overriding. */ withOptions(options: Partial): this { const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ ...this._options, baseURL: this.baseURL, maxRetries: this.maxRetries, timeout: this.timeout, logger: this.logger, logLevel: this.logLevel, fetch: this.fetch, fetchOptions: this.fetchOptions, username: this.username, password: this.password, agentAccessToken: this.agentAccessToken, webhookSignature: this.webhookSignature, ...options, }); return client; } /** * Check whether the base URL is set to its default. */ #baseURLOverridden(): boolean { return this.baseURL !== 'https://api.lightspark.com/grid/2025-10-13'; } protected defaultQuery(): Record | undefined { return this._options.defaultQuery; } protected validateHeaders({ values, nulls }: NullableHeaders) { if (this.username && this.password && values.get('authorization')) { return; } if (nulls.has('authorization')) { return; } if (this.agentAccessToken && values.get('authorization')) { return; } if (nulls.has('authorization')) { return; } if (this.webhookSignature && values.get('x-grid-signature')) { return; } if (nulls.has('x-grid-signature')) { return; } throw new Error( 'Could not resolve authentication method. Expected one of username, password, agentAccessToken or webhookSignature to be set. Or for one of the "Authorization", "Authorization" or "X-Grid-Signature" headers to be explicitly omitted', ); } protected async authHeaders( opts: FinalRequestOptions, schemes: { basicAuth?: boolean; agentAuth?: boolean; webhookSignatureAuth?: boolean }, ): Promise { return buildHeaders([ schemes.basicAuth ? await this.basicAuth(opts) : null, schemes.agentAuth ? await this.agentAuth(opts) : null, schemes.webhookSignatureAuth ? await this.webhookSignatureAuth(opts) : null, ]); } protected async basicAuth(opts: FinalRequestOptions): Promise { if (!this.username) { return undefined; } if (!this.password) { return undefined; } const credentials = `${this.username}:${this.password}`; const Authorization = `Basic ${toBase64(credentials)}`; return buildHeaders([{ Authorization }]); } protected async agentAuth(opts: FinalRequestOptions): Promise { if (this.agentAccessToken == null) { return undefined; } return buildHeaders([{ Authorization: `Bearer ${this.agentAccessToken}` }]); } protected async webhookSignatureAuth(opts: FinalRequestOptions): Promise { if (this.webhookSignature == null) { return undefined; } return buildHeaders([{ 'X-Grid-Signature': this.webhookSignature }]); } protected stringifyQuery(query: object | Record): string { return stringifyQuery(query); } private getUserAgent(): string { return `${this.constructor.name}/JS ${VERSION}`; } protected defaultIdempotencyKey(): string { return `stainless-node-retry-${uuid4()}`; } protected makeStatusError( status: number, error: Object, message: string | undefined, headers: Headers, ): Errors.APIError { return Errors.APIError.generate(status, error, message, headers); } buildURL( path: string, query: Record | null | undefined, defaultBaseURL?: string | undefined, ): string { const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; const url = isAbsoluteURL(path) ? new URL(path) : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); const pathQuery = Object.fromEntries(url.searchParams); if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) { query = { ...pathQuery, ...defaultQuery, ...query }; } if (typeof query === 'object' && query && !Array.isArray(query)) { url.search = this.stringifyQuery(query); } return url.toString(); } /** * Used as a callback for mutating the given `FinalRequestOptions` object. */ protected async prepareOptions(options: FinalRequestOptions): Promise {} /** * Used as a callback for mutating the given `RequestInit` object. * * This is useful for cases where you want to add certain headers based off of * the request properties, e.g. `method` or `url`. */ protected async prepareRequest( request: RequestInit, { url, options }: { url: string; options: FinalRequestOptions }, ): Promise {} get(path: string, opts?: PromiseOrValue): APIPromise { return this.methodRequest('get', path, opts); } post(path: string, opts?: PromiseOrValue): APIPromise { return this.methodRequest('post', path, opts); } patch(path: string, opts?: PromiseOrValue): APIPromise { return this.methodRequest('patch', path, opts); } put(path: string, opts?: PromiseOrValue): APIPromise { return this.methodRequest('put', path, opts); } delete(path: string, opts?: PromiseOrValue): APIPromise { return this.methodRequest('delete', path, opts); } private methodRequest( method: HTTPMethod, path: string, opts?: PromiseOrValue, ): APIPromise { return this.request( Promise.resolve(opts).then((opts) => { return { method, path, ...opts }; }), ); } request( options: PromiseOrValue, remainingRetries: number | null = null, ): APIPromise { return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); } private async makeRequest( optionsInput: PromiseOrValue, retriesRemaining: number | null, retryOfRequestLogID: string | undefined, ): Promise { const options = await optionsInput; const maxRetries = options.maxRetries ?? this.maxRetries; if (retriesRemaining == null) { retriesRemaining = maxRetries; } await this.prepareOptions(options); const { req, url, timeout } = await this.buildRequest(options, { retryCount: maxRetries - retriesRemaining, }); await this.prepareRequest(req, { url, options }); /** Not an API request ID, just for correlating local log entries. */ const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; const startTime = Date.now(); loggerFor(this).debug( `[${requestLogID}] sending request`, formatRequestDetails({ retryOfRequestLogID, method: options.method, url, options, headers: req.headers, }), ); if (options.signal?.aborted) { throw new Errors.APIUserAbortError(); } const controller = new AbortController(); const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); const headersTime = Date.now(); if (response instanceof globalThis.Error) { const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; if (options.signal?.aborted) { throw new Errors.APIUserAbortError(); } // detect native connection timeout errors // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" // others do not provide enough information to distinguish timeouts from other connection errors const isTimeout = isAbortError(response) || /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); if (retriesRemaining) { loggerFor(this).info( `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, ); loggerFor(this).debug( `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, formatRequestDetails({ retryOfRequestLogID, url, durationMs: headersTime - startTime, message: response.message, }), ); return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); } loggerFor(this).info( `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, ); loggerFor(this).debug( `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, formatRequestDetails({ retryOfRequestLogID, url, durationMs: headersTime - startTime, message: response.message, }), ); if (isTimeout) { throw new Errors.APIConnectionTimeoutError(); } throw new Errors.APIConnectionError({ cause: response }); } const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ response.ok ? 'succeeded' : 'failed' } with status ${response.status} in ${headersTime - startTime}ms`; if (!response.ok) { const shouldRetry = await this.shouldRetry(response); if (retriesRemaining && shouldRetry) { const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; // We don't need the body of this response. await Shims.CancelReadableStream(response.body); loggerFor(this).info(`${responseInfo} - ${retryMessage}`); loggerFor(this).debug( `[${requestLogID}] response error (${retryMessage})`, formatRequestDetails({ retryOfRequestLogID, url: response.url, status: response.status, headers: response.headers, durationMs: headersTime - startTime, }), ); return this.retryRequest( options, retriesRemaining, retryOfRequestLogID ?? requestLogID, response.headers, ); } const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; loggerFor(this).info(`${responseInfo} - ${retryMessage}`); const errText = await response.text().catch((err: any) => castToError(err).message); const errJSON = safeJSON(errText) as any; const errMessage = errJSON ? undefined : errText; loggerFor(this).debug( `[${requestLogID}] response error (${retryMessage})`, formatRequestDetails({ retryOfRequestLogID, url: response.url, status: response.status, headers: response.headers, message: errMessage, durationMs: Date.now() - startTime, }), ); const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); throw err; } loggerFor(this).info(responseInfo); loggerFor(this).debug( `[${requestLogID}] response start`, formatRequestDetails({ retryOfRequestLogID, url: response.url, status: response.status, headers: response.headers, durationMs: headersTime - startTime, }), ); return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; } getAPIList = Pagination.AbstractPage>( path: string, Page: new (...args: any[]) => PageClass, opts?: PromiseOrValue, ): Pagination.PagePromise { return this.requestAPIList( Page, opts && 'then' in opts ? opts.then((opts) => ({ method: 'get', path, ...opts })) : { method: 'get', path, ...opts }, ); } requestAPIList< Item = unknown, PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, >( Page: new (...args: ConstructorParameters) => PageClass, options: PromiseOrValue, ): Pagination.PagePromise { const request = this.makeRequest(options, null, undefined); return new Pagination.PagePromise(this as any as LightsparkGrid, request, Page); } async fetchWithTimeout( url: RequestInfo, init: RequestInit | undefined, ms: number, controller: AbortController, ): Promise { const { signal, method, ...options } = init || {}; const abort = this._makeAbort(controller); if (signal) signal.addEventListener('abort', abort, { once: true }); const timeout = setTimeout(abort, ms); const isReadableBody = ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); const fetchOptions: RequestInit = { signal: controller.signal as any, ...(isReadableBody ? { duplex: 'half' } : {}), method: 'GET', ...options, }; if (method) { // Custom methods like 'patch' need to be uppercased // See https://github.com/nodejs/undici/issues/2294 fetchOptions.method = method.toUpperCase(); } try { // use undefined this binding; fetch errors if bound to something else in browser/cloudflare return await this.fetch.call(undefined, url, fetchOptions); } finally { clearTimeout(timeout); } } private async shouldRetry(response: Response): Promise { // Note this is not a standard header. const shouldRetryHeader = response.headers.get('x-should-retry'); // If the server explicitly says whether or not to retry, obey. if (shouldRetryHeader === 'true') return true; if (shouldRetryHeader === 'false') return false; // Retry on request timeouts. if (response.status === 408) return true; // Retry on lock timeouts. if (response.status === 409) return true; // Retry on rate limits. if (response.status === 429) return true; // Retry internal errors. if (response.status >= 500) return true; return false; } private async retryRequest( options: FinalRequestOptions, retriesRemaining: number, requestLogID: string, responseHeaders?: Headers | undefined, ): Promise { let timeoutMillis: number | undefined; // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); if (retryAfterMillisHeader) { const timeoutMs = parseFloat(retryAfterMillisHeader); if (!Number.isNaN(timeoutMs)) { timeoutMillis = timeoutMs; } } // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After const retryAfterHeader = responseHeaders?.get('retry-after'); if (retryAfterHeader && !timeoutMillis) { const timeoutSeconds = parseFloat(retryAfterHeader); if (!Number.isNaN(timeoutSeconds)) { timeoutMillis = timeoutSeconds * 1000; } else { timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); } } // If the API asks us to wait a certain amount of time, just do what it // says, but otherwise calculate a default if (timeoutMillis === undefined) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } await sleep(timeoutMillis); return this.makeRequest(options, retriesRemaining - 1, requestLogID); } private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { const initialRetryDelay = 0.5; const maxRetryDelay = 8.0; const numRetries = maxRetries - retriesRemaining; // Apply exponential backoff, but not more than the max. const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); // Apply some jitter, take up to at most 25 percent of the retry time. const jitter = 1 - Math.random() * 0.25; return sleepSeconds * jitter * 1000; } async buildRequest( inputOptions: FinalRequestOptions, { retryCount = 0 }: { retryCount?: number } = {}, ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { const options = { ...inputOptions }; const { method, path, query, defaultBaseURL } = options; const url = this.buildURL(path!, query as Record, defaultBaseURL); if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); const req: FinalizedRequestInit = { method, headers: reqHeaders, ...(options.signal && { signal: options.signal }), ...((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), ...(body && { body }), ...((this.fetchOptions as any) ?? {}), ...((options.fetchOptions as any) ?? {}), }; return { req, url, timeout: options.timeout }; } private async buildHeaders({ options, method, bodyHeaders, retryCount, }: { options: FinalRequestOptions; method: HTTPMethod; bodyHeaders: HeadersLike; retryCount: number; }): Promise { let idempotencyHeaders: HeadersLike = {}; if (this.idempotencyHeader && method !== 'get') { if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; } const headers = buildHeaders([ idempotencyHeaders, { Accept: 'application/json', 'User-Agent': this.getUserAgent(), 'X-Stainless-Retry-Count': String(retryCount), ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), ...getPlatformHeaders(), }, await this.authHeaders( options, options.__security ?? { basicAuth: true, agentAuth: true, webhookSignatureAuth: true }, ), this._options.defaultHeaders, bodyHeaders, options.headers, ]); this.validateHeaders(headers); return headers.values; } private _makeAbort(controller: AbortController) { // note: we can't just inline this method inside `fetchWithTimeout()` because then the closure // would capture all request options, and cause a memory leak. return () => controller.abort(); } private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { bodyHeaders: HeadersLike; body: BodyInit | undefined; } { if (!body) { return { bodyHeaders: undefined, body: undefined }; } const headers = buildHeaders([rawHeaders]); if ( // Pass raw type verbatim ArrayBuffer.isView(body) || body instanceof ArrayBuffer || body instanceof DataView || (typeof body === 'string' && // Preserve legacy string encoding behavior for now headers.values.has('content-type')) || // `Blob` is superset of `File` ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || // `FormData` -> `multipart/form-data` body instanceof FormData || // `URLSearchParams` -> `application/x-www-form-urlencoded` body instanceof URLSearchParams || // Send chunked stream (each chunk has own `length`) ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) ) { return { bodyHeaders: undefined, body: body as BodyInit }; } else if ( typeof body === 'object' && (Symbol.asyncIterator in body || (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) ) { return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; } else if ( typeof body === 'object' && headers.values.get('content-type') === 'application/x-www-form-urlencoded' ) { return { bodyHeaders: { 'content-type': 'application/x-www-form-urlencoded' }, body: this.stringifyQuery(body), }; } else { return this.#encoder({ body, headers }); } } static LightsparkGrid = this; static DEFAULT_TIMEOUT = 60000; // 1 minute static LightsparkGridError = Errors.LightsparkGridError; static APIError = Errors.APIError; static APIConnectionError = Errors.APIConnectionError; static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; static APIUserAbortError = Errors.APIUserAbortError; static NotFoundError = Errors.NotFoundError; static ConflictError = Errors.ConflictError; static RateLimitError = Errors.RateLimitError; static BadRequestError = Errors.BadRequestError; static AuthenticationError = Errors.AuthenticationError; static InternalServerError = Errors.InternalServerError; static PermissionDeniedError = Errors.PermissionDeniedError; static UnprocessableEntityError = Errors.UnprocessableEntityError; static toFile = Uploads.toFile; /** * Platform configuration endpoints for managing global settings. You can also configure these settings in the Grid dashboard. */ config: API.Config = new API.Config(this); customers: API.Customers = new API.Customers(this); /** * Internal account management endpoints for creating and managing internal accounts */ platform: API.Platform = new API.Platform(this); /** * Endpoints for transferring funds between internal and external accounts with the same currency */ transferIn: API.TransferIn = new API.TransferIn(this); /** * Endpoints for transferring funds between internal and external accounts with the same currency */ transferOut: API.TransferOut = new API.TransferOut(this); /** * Endpoints for creating and confirming quotes for cross-currency transfers */ receiver: API.Receiver = new API.Receiver(this); /** * Endpoints for creating and confirming quotes for cross-currency transfers */ quotes: API.Quotes = new API.Quotes(this); /** * Endpoints for retrieving transaction information */ transactions: API.Transactions = new API.Transactions(this); /** * Endpoints for creating, claiming and managing UMA invitations */ invitations: API.Invitations = new API.Invitations(this); /** * Endpoints to trigger test cases in sandbox */ sandbox: API.Sandbox = new API.Sandbox(this); umaProviders: API.UmaProviders = new API.UmaProviders(this); /** * Endpoints to programmatically manage API tokens */ tokens: API.Tokens = new API.Tokens(this); /** * Endpoints for retrieving cached foreign exchange rates. Rates are cached for approximately 5 minutes and include platform-specific fees. */ exchangeRates: API.ExchangeRates = new API.ExchangeRates(this); webhooks: API.Webhooks = new API.Webhooks(this); /** * Endpoints for creating and confirming quotes for cross-currency transfers */ crypto: API.Crypto = new API.Crypto(this); /** * Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. */ beneficialOwners: API.BeneficialOwners = new API.BeneficialOwners(this); /** * Endpoints for uploading and managing verification documents for customers and beneficial owners. Supports KYC and KYB document requirements. */ documents: API.Documents = new API.Documents(this); /** * Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers. */ verifications: API.Verifications = new API.Verifications(this); /** * Endpoints for discovering available payment rails, banks, and providers for a given country and currency corridor. */ discoveries: API.Discoveries = new API.Discoveries(this); auth: API.Auth = new API.Auth(this); /** * Endpoints for creating and managing agents (experimental), called by the partner's backend using platform credentials. Covers the full agent lifecycle: creation, policy configuration, pausing, deletion, the device code installation flow, and approving or rejecting transactions initiated by agents. */ agents: API.Agents = new API.Agents(this); /** * Card management endpoints. Issue debit cards against an internal account, freeze / unfreeze, close, manage card funding sources, and list card transactions. */ cards: API.Cards = new API.Cards(this); } LightsparkGrid.Config = Config; LightsparkGrid.Customers = Customers; LightsparkGrid.Platform = Platform; LightsparkGrid.TransferIn = TransferIn; LightsparkGrid.TransferOut = TransferOut; LightsparkGrid.Receiver = Receiver; LightsparkGrid.Quotes = Quotes; LightsparkGrid.Transactions = Transactions; LightsparkGrid.Invitations = Invitations; LightsparkGrid.Sandbox = Sandbox; LightsparkGrid.UmaProviders = UmaProviders; LightsparkGrid.Tokens = Tokens; LightsparkGrid.ExchangeRates = ExchangeRates; LightsparkGrid.Webhooks = Webhooks; LightsparkGrid.Crypto = Crypto; LightsparkGrid.BeneficialOwners = BeneficialOwners; LightsparkGrid.Documents = Documents; LightsparkGrid.Verifications = Verifications; LightsparkGrid.Discoveries = Discoveries; LightsparkGrid.Auth = Auth; LightsparkGrid.Agents = Agents; LightsparkGrid.Cards = Cards; export declare namespace LightsparkGrid { export type RequestOptions = Opts.RequestOptions; export import DefaultPagination = Pagination.DefaultPagination; export { type DefaultPaginationParams as DefaultPaginationParams, type DefaultPaginationResponse as DefaultPaginationResponse, }; export { Config as Config, type CustomerInfoFieldName as CustomerInfoFieldName, type EmbeddedWalletConfig as EmbeddedWalletConfig, type PlatformConfig as PlatformConfig, type PlatformCurrencyConfig as PlatformCurrencyConfig, type ConfigUpdateParams as ConfigUpdateParams, }; export { Customers as Customers, type CustomerCreateResponse as CustomerCreateResponse, type CustomerRetrieveResponse as CustomerRetrieveResponse, type CustomerUpdateResponse as CustomerUpdateResponse, type CustomerListResponse as CustomerListResponse, type CustomerDeleteResponse as CustomerDeleteResponse, type CustomerExportResponse as CustomerExportResponse, type CustomerGenerateKYCLinkResponse as CustomerGenerateKYCLinkResponse, type CustomerListResponsesDefaultPagination as CustomerListResponsesDefaultPagination, type CustomerCreateParams as CustomerCreateParams, type CustomerUpdateParams as CustomerUpdateParams, type CustomerListParams as CustomerListParams, type CustomerExportParams as CustomerExportParams, type CustomerGenerateKYCLinkParams as CustomerGenerateKYCLinkParams, type CustomerListInternalAccountsParams as CustomerListInternalAccountsParams, type CustomerUpdateInternalAccountParams as CustomerUpdateInternalAccountParams, }; export { Platform as Platform, type PlatformListInternalAccountsResponse as PlatformListInternalAccountsResponse, type PlatformListInternalAccountsParams as PlatformListInternalAccountsParams, }; export { TransferIn as TransferIn, type BaseTransactionDestination as BaseTransactionDestination, type ExternalAccountReference as ExternalAccountReference, type InternalAccountReference as InternalAccountReference, type Transaction as Transaction, type TransferInCreateParams as TransferInCreateParams, }; export { TransferOut as TransferOut, type TransferOutCreateParams as TransferOutCreateParams }; export { Receiver as Receiver, type CounterpartyFieldDefinition as CounterpartyFieldDefinition, type LookupResponse as LookupResponse, type ReceiverLookupExternalAccountResponse as ReceiverLookupExternalAccountResponse, type ReceiverLookupUmaResponse as ReceiverLookupUmaResponse, type ReceiverLookupExternalAccountParams as ReceiverLookupExternalAccountParams, type ReceiverLookupUmaParams as ReceiverLookupUmaParams, }; export { Quotes as Quotes, type BaseDestination as BaseDestination, type BaseQuoteSource as BaseQuoteSource, type Currency as Currency, type OutgoingRateDetails as OutgoingRateDetails, type PaymentInstructions as PaymentInstructions, type Quote as Quote, type QuoteDestinationOneOf as QuoteDestinationOneOf, type QuoteSourceOneOf as QuoteSourceOneOf, type QuoteCreateParams as QuoteCreateParams, type QuoteExecuteParams as QuoteExecuteParams, }; export { Transactions as Transactions, type BaseTransactionSource as BaseTransactionSource, type IncomingRateDetails as IncomingRateDetails, type IncomingTransaction as IncomingTransaction, type OutgoingTransaction as OutgoingTransaction, type OutgoingTransactionStatus as OutgoingTransactionStatus, type ReconciliationInstructions as ReconciliationInstructions, type TransactionSourceOneOf as TransactionSourceOneOf, type TransactionStatus as TransactionStatus, type TransactionType as TransactionType, type TransactionListParams as TransactionListParams, type TransactionApproveParams as TransactionApproveParams, type TransactionRejectParams as TransactionRejectParams, }; export { Invitations as Invitations, type CurrencyAmount as CurrencyAmount, type UmaInvitation as UmaInvitation, type InvitationCreateParams as InvitationCreateParams, type InvitationClaimParams as InvitationClaimParams, }; export { Sandbox as Sandbox, type SandboxSendFundsParams as SandboxSendFundsParams }; export { UmaProviders as UmaProviders, type UmaProviderListResponse as UmaProviderListResponse, type UmaProviderListResponsesDefaultPagination as UmaProviderListResponsesDefaultPagination, type UmaProviderListParams as UmaProviderListParams, }; export { Tokens as Tokens, type APIToken as APIToken, type Permission as Permission, type APITokensDefaultPagination as APITokensDefaultPagination, type TokenCreateParams as TokenCreateParams, type TokenListParams as TokenListParams, }; export { ExchangeRates as ExchangeRates, type ExchangeRateListResponse as ExchangeRateListResponse, type ExchangeRateListParams as ExchangeRateListParams, }; export { Webhooks as Webhooks, type AgentActionWebhookEvent as AgentActionWebhookEvent, type IncomingPaymentWebhookEvent as IncomingPaymentWebhookEvent, type OutgoingPaymentWebhookEvent as OutgoingPaymentWebhookEvent, type TestWebhookWebhookEvent as TestWebhookWebhookEvent, type BulkUploadWebhookEvent as BulkUploadWebhookEvent, type InvitationClaimedWebhookEvent as InvitationClaimedWebhookEvent, type CustomerUpdateWebhookEvent as CustomerUpdateWebhookEvent, type InternalAccountStatusWebhookEvent as InternalAccountStatusWebhookEvent, type VerificationUpdateWebhookEvent as VerificationUpdateWebhookEvent, type CardStateChangeWebhookEvent as CardStateChangeWebhookEvent, type CardFundingSourceChangeWebhookEvent as CardFundingSourceChangeWebhookEvent, type UnwrapWebhookEvent as UnwrapWebhookEvent, }; export { Crypto as Crypto, type CryptoEstimateWithdrawalFeeResponse as CryptoEstimateWithdrawalFeeResponse, type CryptoEstimateWithdrawalFeeParams as CryptoEstimateWithdrawalFeeParams, }; export { BeneficialOwners as BeneficialOwners, type BeneficialOwnerPersonalInfo as BeneficialOwnerPersonalInfo, type BeneficialOwnerCreateParams as BeneficialOwnerCreateParams, type BeneficialOwnerUpdateParams as BeneficialOwnerUpdateParams, type BeneficialOwnerListParams as BeneficialOwnerListParams, }; export { Documents as Documents, type DocumentRetrieveResponse as DocumentRetrieveResponse, type DocumentListResponse as DocumentListResponse, type DocumentReplaceResponse as DocumentReplaceResponse, type DocumentUploadResponse as DocumentUploadResponse, type DocumentListResponsesDefaultPagination as DocumentListResponsesDefaultPagination, type DocumentListParams as DocumentListParams, type DocumentReplaceParams as DocumentReplaceParams, type DocumentUploadParams as DocumentUploadParams, }; export { Verifications as Verifications, type VerificationRetrieveResponse as VerificationRetrieveResponse, type VerificationListResponse as VerificationListResponse, type VerificationSubmitResponse as VerificationSubmitResponse, type VerificationListResponsesDefaultPagination as VerificationListResponsesDefaultPagination, type VerificationListParams as VerificationListParams, type VerificationSubmitParams as VerificationSubmitParams, }; export { Discoveries as Discoveries, type DiscoveryListResponse as DiscoveryListResponse, type DiscoveryListParams as DiscoveryListParams, }; export { Auth as Auth }; export { Agents as Agents, type Agent as Agent, type AgentAccountRestrictions as AgentAccountRestrictions, type AgentAccountRule as AgentAccountRule, type AgentAction as AgentAction, type AgentActionListResponse as AgentActionListResponse, type AgentActionRejectRequest as AgentActionRejectRequest, type AgentApprovalThresholds as AgentApprovalThresholds, type AgentCreateRequest as AgentCreateRequest, type AgentCreateResponse as AgentCreateResponse, type AgentDeviceCode as AgentDeviceCode, type AgentDeviceCodeRedeemResponse as AgentDeviceCodeRedeemResponse, type AgentDeviceCodeStatusResponse as AgentDeviceCodeStatusResponse, type AgentListResponse as AgentListResponse, type AgentPolicy as AgentPolicy, type AgentUpdateRequest as AgentUpdateRequest, type AgentUsage as AgentUsage, type AgentsDefaultPagination as AgentsDefaultPagination, type AgentActionsDefaultPagination as AgentActionsDefaultPagination, type AgentCreateParams as AgentCreateParams, type AgentUpdateParams as AgentUpdateParams, type AgentListParams as AgentListParams, type AgentListApprovalsParams as AgentListApprovalsParams, type AgentUpdatePolicyParams as AgentUpdatePolicyParams, }; export { Cards as Cards, type CardRetrieveResponse as CardRetrieveResponse, type CardUpdateResponse as CardUpdateResponse, type CardListResponse as CardListResponse, type CardIssueResponse as CardIssueResponse, type CardListResponsesDefaultPagination as CardListResponsesDefaultPagination, type CardUpdateParams as CardUpdateParams, type CardListParams as CardListParams, type CardIssueParams as CardIssueParams, }; export type AedBeneficiary = API.AedBeneficiary; export type AedExternalAccountCreateInfo = API.AedExternalAccountCreateInfo; export type AgentTransferDetails = API.AgentTransferDetails; export type BdtBeneficiary = API.BdtBeneficiary; export type BdtExternalAccountCreateInfo = API.BdtExternalAccountCreateInfo; export type BeneficialOwner = API.BeneficialOwner; export type BrlExternalAccountCreateInfo = API.BrlExternalAccountCreateInfo; export type BulkCustomerImportErrorEntry = API.BulkCustomerImportErrorEntry; export type BusinessCustomer = API.BusinessCustomer; export type BusinessInfoUpdate = API.BusinessInfoUpdate; export type BwpBeneficiary = API.BwpBeneficiary; export type BwpExternalAccountCreateInfo = API.BwpExternalAccountCreateInfo; export type CadBeneficiary = API.CadBeneficiary; export type CadExternalAccountCreateInfo = API.CadExternalAccountCreateInfo; export type CopBeneficiary = API.CopBeneficiary; export type CopExternalAccountCreateInfo = API.CopExternalAccountCreateInfo; export type DkkExternalAccountCreateInfo = API.DkkExternalAccountCreateInfo; export type EgpBeneficiary = API.EgpBeneficiary; export type EgpExternalAccountCreateInfo = API.EgpExternalAccountCreateInfo; export type EthereumWalletExternalAccountInfo = API.EthereumWalletExternalAccountInfo; export type EurBeneficiary = API.EurBeneficiary; export type EurExternalAccountCreateInfo = API.EurExternalAccountCreateInfo; export type GbpExternalAccountCreateInfo = API.GbpExternalAccountCreateInfo; export type GhsBeneficiary = API.GhsBeneficiary; export type GhsExternalAccountCreateInfo = API.GhsExternalAccountCreateInfo; export type GtqBeneficiary = API.GtqBeneficiary; export type GtqExternalAccountCreateInfo = API.GtqExternalAccountCreateInfo; export type HkdExternalAccountCreateInfo = API.HkdExternalAccountCreateInfo; export type HtgBeneficiary = API.HtgBeneficiary; export type HtgExternalAccountCreateInfo = API.HtgExternalAccountCreateInfo; export type IdrExternalAccountCreateInfo = API.IdrExternalAccountCreateInfo; export type IndividualCustomer = API.IndividualCustomer; export type InrExternalAccountCreateInfo = API.InrExternalAccountCreateInfo; export type JmdBeneficiary = API.JmdBeneficiary; export type JmdExternalAccountCreateInfo = API.JmdExternalAccountCreateInfo; export type KesBeneficiary = API.KesBeneficiary; export type KesExternalAccountCreateInfo = API.KesExternalAccountCreateInfo; export type MwkBeneficiary = API.MwkBeneficiary; export type MwkExternalAccountCreateInfo = API.MwkExternalAccountCreateInfo; export type MxnExternalAccountCreateInfo = API.MxnExternalAccountCreateInfo; export type MyrExternalAccountCreateInfo = API.MyrExternalAccountCreateInfo; export type NgnBeneficiary = API.NgnBeneficiary; export type NgnExternalAccountCreateInfo = API.NgnExternalAccountCreateInfo; export type PhpExternalAccountCreateInfo = API.PhpExternalAccountCreateInfo; export type PkrBeneficiary = API.PkrBeneficiary; export type PkrExternalAccountCreateInfo = API.PkrExternalAccountCreateInfo; export type RwfBeneficiary = API.RwfBeneficiary; export type RwfExternalAccountCreateInfo = API.RwfExternalAccountCreateInfo; export type SgdExternalAccountCreateInfo = API.SgdExternalAccountCreateInfo; export type SlvBeneficiary = API.SlvBeneficiary; export type SlvExternalAccountCreateInfo = API.SlvExternalAccountCreateInfo; export type SwiftBeneficiary = API.SwiftBeneficiary; export type SwiftExternalAccountCreateInfo = API.SwiftExternalAccountCreateInfo; export type ThbExternalAccountCreateInfo = API.ThbExternalAccountCreateInfo; export type TzsBeneficiary = API.TzsBeneficiary; export type TzsExternalAccountCreateInfo = API.TzsExternalAccountCreateInfo; export type UgxBeneficiary = API.UgxBeneficiary; export type UgxExternalAccountCreateInfo = API.UgxExternalAccountCreateInfo; export type UsdExternalAccountCreateInfo = API.UsdExternalAccountCreateInfo; export type VerificationError = API.VerificationError; export type VndExternalAccountCreateInfo = API.VndExternalAccountCreateInfo; export type XafBeneficiary = API.XafBeneficiary; export type XafExternalAccountCreateInfo = API.XafExternalAccountCreateInfo; export type XofBeneficiary = API.XofBeneficiary; export type XofExternalAccountCreateInfo = API.XofExternalAccountCreateInfo; export type ZarBeneficiary = API.ZarBeneficiary; export type ZarExternalAccountCreateInfo = API.ZarExternalAccountCreateInfo; export type ZmwBeneficiary = API.ZmwBeneficiary; export type ZmwExternalAccountCreateInfo = API.ZmwExternalAccountCreateInfo; }