@stable_sea/sdk 0.2.24 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -340,6 +340,23 @@ const valid = verifyWebhookSignature(
340
340
  );
341
341
  ```
342
342
 
343
+ Subscribe to the lifecycle events you need when creating or updating a webhook
344
+ endpoint:
345
+
346
+ | Event | Meaning |
347
+ | --- | --- |
348
+ | `transfer_approval.created` | A request is pending human approval. |
349
+ | `transfer_approval.updated` | The request reached a terminal approval state. Partial votes do not emit this event. |
350
+ | `withdrawal.created` | A withdrawal request was accepted. |
351
+ | `withdrawal.updated` | The withdrawal completed, failed or was canceled. A later bank return can produce another update. |
352
+
353
+ Events contain resource pointers. Retrieve a withdrawal with
354
+ `GET /v1/withdrawals/{id}`; its current state is authoritative. To read an approval,
355
+ use an authenticated member session and query `/money/transfer_approvals` with
356
+ `request_id`, or `/money/test_transfer_approvals` for Test events. Existing member
357
+ visibility applies. API keys cannot cast human votes. Delivery is asynchronous;
358
+ handle duplicate event IDs and changes that arrive out of order.
359
+
343
360
  API request, response, pagination, and error types are generated from
344
361
  `services/backend/api/openapi.public.json` and exported as `paths`, `operations`,
345
362
  and `components`.
@@ -575,3 +592,43 @@ adopt new/reclassified assets, review `fund_catalog_changes`, then use the curre
575
592
  Preview checks saved settings and does not authorize execution. API keys do not
576
593
  act as the key owner's member; machine `/v1/policies` remains business controls
577
594
  and tiers, without member-policy administration.
595
+
596
+
597
+ ## Withdraw funds
598
+
599
+ Create a key with **Withdraw funds** (`money:withdrawals_write`) and **Read money**
600
+ (`money:read`) in Terminal's existing API keys pane. Test keys use the same
601
+ workflow with simulated funds and do not require a two-factor code.
602
+
603
+ For on-chain withdrawals, list `/v1/external_wallets` and select an enabled saved
604
+ destination. Its `symbol` identifies the withdrawal asset and `payment_rail` the
605
+ network; holdings can contain other assets. Submit `/v1/withdrawals` with
606
+ `method: "onchain"`, `external_wallet` and an amount in that asset's units.
607
+
608
+ For US ACH or domestic wire, create `/v1/withdrawal_quotes` with `method: "ach"`
609
+ or `"wire"`, the saved `bank_account` ID and a USD decimal amount with at most two fractional digits.
610
+ Retrieve the quote until `status` is `ready`, review `pricing`, then submit
611
+ `/v1/withdrawals` with the same method, bank and amount plus `quote_id`. Quotes
612
+ expire after five minutes and serve one withdrawal. Acceptance queues work without
613
+ booking funds. Processing must begin before `expires_ts`; queue delay or retries
614
+ can produce `quote_invalid`. Pending human approval preserves those fee terms
615
+ after quote expiry.
616
+
617
+ Use a stable `Idempotency-Key` for each write. Retrieve the withdrawal to follow
618
+ its status. `pending_approval` requires a human decision through the existing
619
+ approval workflow; it reserves no funds. Once booked, `transaction_id` links to
620
+ the native transaction and its settlement details. Request cancellation with
621
+ `POST /v1/withdrawals/{id}/cancel` while pending; `canceling` is not confirmation.
622
+ Wait for `canceled`, or follow the transaction if booking already won the race.
623
+ Cancellation requires the credential that created the withdrawal. A replacement
624
+ key can read the business's withdrawals but cannot cancel its predecessor's requests.
625
+
626
+ A failed bank payout can require recovery after conversion. `failed` does not
627
+ mean the principal has been refunded or is available to spend. A confirmed ACH
628
+ return also makes the original operation `failed` while preserving its settled
629
+ ledger entries. Read the transaction's `financial_details` for fee recovery;
630
+ a queued fee refund is separate from both principal recovery and a settled refund.
631
+
632
+ The [Sandbox test project](consumer-test/README.md#test-a-withdrawal) includes
633
+ `npm run test:withdrawal` for a saved Test destination. It preserves request keys
634
+ and receipts so an interrupted run can resume without creating a new withdrawal.
@@ -1105,6 +1105,114 @@ export interface paths {
1105
1105
  patch?: never;
1106
1106
  trace?: never;
1107
1107
  };
1108
+ "/v1/withdrawal_quotes": {
1109
+ parameters: {
1110
+ query?: never;
1111
+ header?: never;
1112
+ path?: never;
1113
+ cookie?: never;
1114
+ };
1115
+ get?: never;
1116
+ put?: never;
1117
+ /**
1118
+ * Create a withdrawal quote
1119
+ * @description Quotes an ACH or US domestic wire withdrawal to a saved US bank account. No money moves. Poll until ready, then submit the exact method, bank and amount with its quote_id before expiry. One quote serves one withdrawal; retries do not extend expiry. On-chain withdrawals use their saved wallet directly and do not require a bank quote.
1120
+ */
1121
+ post: operations["withdrawal_quotes.create"];
1122
+ delete?: never;
1123
+ options?: never;
1124
+ head?: never;
1125
+ patch?: never;
1126
+ trace?: never;
1127
+ };
1128
+ "/v1/withdrawal_quotes/{id}": {
1129
+ parameters: {
1130
+ query?: never;
1131
+ header?: never;
1132
+ path?: never;
1133
+ cookie?: never;
1134
+ };
1135
+ /**
1136
+ * Retrieve a withdrawal quote
1137
+ * @description Returns a bank withdrawal quote belonging to this business and API mode. Expiry remains fixed even when a ready quote is retrieved again.
1138
+ */
1139
+ get: operations["withdrawal_quotes.get"];
1140
+ put?: never;
1141
+ post?: never;
1142
+ delete?: never;
1143
+ options?: never;
1144
+ head?: never;
1145
+ patch?: never;
1146
+ trace?: never;
1147
+ };
1148
+ "/v1/withdrawals": {
1149
+ parameters: {
1150
+ query?: never;
1151
+ header?: never;
1152
+ path?: never;
1153
+ cookie?: never;
1154
+ };
1155
+ /**
1156
+ * List withdrawals
1157
+ * @description Lists this business's API withdrawals, newest first. Filter by method or paginate with one withdrawal ID cursor at a time. Native transaction history remains available separately.
1158
+ */
1159
+ get: operations["withdrawals.list"];
1160
+ put?: never;
1161
+ /**
1162
+ * Create a withdrawal
1163
+ * @description Queues a withdrawal to a saved external wallet or US bank account. Requires money:withdrawals_write and an Idempotency-Key. Reuse the same key and body after a lost response to recover the original request.
1164
+ *
1165
+ * Eligibility, funds, fees, limits and approval rules are enforced before booking. Pending human approval does not reserve funds; use the member client or Terminal to decide it. Follow status and transaction_id through settlement.
1166
+ *
1167
+ * For ACH and wire, processing must start before the quote's expires_ts. Queue delay or retries can produce quote_invalid after acceptance. Once human approval is pending, its agreed fees remain valid after quote expiry.
1168
+ */
1169
+ post: operations["withdrawals.create"];
1170
+ delete?: never;
1171
+ options?: never;
1172
+ head?: never;
1173
+ patch?: never;
1174
+ trace?: never;
1175
+ };
1176
+ "/v1/withdrawals/{id}": {
1177
+ parameters: {
1178
+ query?: never;
1179
+ header?: never;
1180
+ path?: never;
1181
+ cookie?: never;
1182
+ };
1183
+ /**
1184
+ * Retrieve a withdrawal
1185
+ * @description Returns one withdrawal, its retained approval reference and its native transaction outcome. A withdrawal belonging to another business or API mode is not found.
1186
+ */
1187
+ get: operations["withdrawals.get"];
1188
+ put?: never;
1189
+ post?: never;
1190
+ delete?: never;
1191
+ options?: never;
1192
+ head?: never;
1193
+ patch?: never;
1194
+ trace?: never;
1195
+ };
1196
+ "/v1/withdrawals/{id}/cancel": {
1197
+ parameters: {
1198
+ query?: never;
1199
+ header?: never;
1200
+ path?: never;
1201
+ cookie?: never;
1202
+ };
1203
+ get?: never;
1204
+ put?: never;
1205
+ /**
1206
+ * Cancel a withdrawal
1207
+ * @description Requests cancellation of an unbooked withdrawal created by this credential. Poll until canceled or another final outcome. Cancellation is asynchronous: a concurrent final approval or booking can win, in which case the withdrawal continues and its transaction remains visible. A booked withdrawal cannot be canceled.
1208
+ */
1209
+ post: operations["withdrawals.cancel"];
1210
+ delete?: never;
1211
+ options?: never;
1212
+ head?: never;
1213
+ patch?: never;
1214
+ trace?: never;
1215
+ };
1108
1216
  }
1109
1217
  export type webhooks = Record<string, never>;
1110
1218
  export interface components {
@@ -1463,7 +1571,7 @@ export interface components {
1463
1571
  object: "list";
1464
1572
  };
1465
1573
  /** @enum {string} */
1466
- EventTypeName: "bank_account.created" | "bank_account.removed" | "bank_account_operation.updated" | "deposit.updated" | "deposit_quote.updated" | "external_wallet.created" | "external_wallet.updated" | "external_wallet_operation.updated" | "notification.created" | "order.updated" | "source_wallet_declaration.updated" | "statement_generation.updated" | "transaction.created" | "transaction.updated";
1574
+ EventTypeName: "bank_account.created" | "bank_account.removed" | "bank_account_operation.updated" | "deposit.updated" | "deposit_quote.updated" | "external_wallet.created" | "external_wallet.updated" | "external_wallet_operation.updated" | "notification.created" | "order.updated" | "source_wallet_declaration.updated" | "statement_generation.updated" | "transaction.created" | "transaction.updated" | "transfer_approval.created" | "transfer_approval.updated" | "withdrawal.created" | "withdrawal.updated";
1467
1575
  ExternalWallet: {
1468
1576
  /** @description True when a Stable Sea operator registered this wallet for the business; false when the business added it itself. */
1469
1577
  added_by_stable_sea: boolean;
@@ -1508,6 +1616,8 @@ export interface components {
1508
1616
  * @enum {string}
1509
1617
  */
1510
1618
  status: "enabled" | "disabled";
1619
+ /** @description The asset this saved destination accepts for withdrawals. Address-level holdings can contain other assets. */
1620
+ symbol: string;
1511
1621
  };
1512
1622
  ExternalWalletCreateInput: {
1513
1623
  address: string;
@@ -2793,6 +2903,82 @@ export interface components {
2793
2903
  enabled_events?: components["schemas"]["EventTypeName"][];
2794
2904
  url?: string | null;
2795
2905
  };
2906
+ Withdrawal: {
2907
+ amount: string;
2908
+ approval_request_id: string | null;
2909
+ bank_account: string | null;
2910
+ /** Format: date-time */
2911
+ created_ts: string;
2912
+ external_wallet: string | null;
2913
+ failure_code?: string;
2914
+ id: string;
2915
+ livemode: boolean;
2916
+ /** @enum {string} */
2917
+ method: "onchain" | "ach" | "wire";
2918
+ /** @enum {string} */
2919
+ object: "withdrawal";
2920
+ quote_id: string | null;
2921
+ /** @enum {string} */
2922
+ status: "pending" | "pending_approval" | "canceling" | "processing" | "completed" | "failed" | "canceled";
2923
+ transaction_id: string | null;
2924
+ };
2925
+ WithdrawalFeeTerms: {
2926
+ bank_fee_usd: string;
2927
+ /** Format: int64 */
2928
+ bps: number;
2929
+ caps_at_par: boolean;
2930
+ conversion_input: string;
2931
+ estimated_conversion_rate?: string | null;
2932
+ estimated_proceeds?: string | null;
2933
+ platform_fee_usdc: string;
2934
+ /** @enum {string} */
2935
+ settlement_asset: "USD";
2936
+ settles_at_par: boolean;
2937
+ /** @enum {string} */
2938
+ source_symbol: "USDC";
2939
+ total_source_debit: string;
2940
+ };
2941
+ WithdrawalInput: {
2942
+ /** @description Principal excluding fees: USD for ACH/wire, or units of the saved wallet's asset for on-chain withdrawals. */
2943
+ amount: string;
2944
+ bank_account?: string;
2945
+ external_wallet?: string;
2946
+ /** @enum {string} */
2947
+ method: "onchain" | "ach" | "wire";
2948
+ /** @description A ready, unexpired quote for this exact bank, method and amount. Required for ACH/wire; omit for on-chain withdrawals. */
2949
+ quote_id?: string | null;
2950
+ };
2951
+ WithdrawalList: {
2952
+ data: components["schemas"]["Withdrawal"][];
2953
+ has_more: boolean;
2954
+ /** @enum {string} */
2955
+ object: "list";
2956
+ };
2957
+ WithdrawalQuote: {
2958
+ amount: string;
2959
+ bank_account: string;
2960
+ /** Format: date-time */
2961
+ created_ts: string;
2962
+ /** Format: date-time */
2963
+ expires_ts: string;
2964
+ failure_code?: string;
2965
+ id: string;
2966
+ livemode: boolean;
2967
+ /** @enum {string} */
2968
+ method: "ach" | "wire";
2969
+ /** @enum {string} */
2970
+ object: "withdrawal_quote";
2971
+ pricing: components["schemas"]["WithdrawalFeeTerms"] | null;
2972
+ /** @enum {string} */
2973
+ status: "pending" | "ready" | "failed";
2974
+ };
2975
+ WithdrawalQuoteInput: {
2976
+ /** @description USD principal in whole cents, excluding fees. */
2977
+ amount: string;
2978
+ bank_account: string;
2979
+ /** @enum {string} */
2980
+ method: "ach" | "wire";
2981
+ };
2796
2982
  };
2797
2983
  responses: {
2798
2984
  /** @description Invalid input, version, account directive, or idempotency header. Correct the request before retrying. */
@@ -5070,4 +5256,248 @@ export interface operations {
5070
5256
  };
5071
5257
  };
5072
5258
  };
5259
+ "withdrawal_quotes.create": {
5260
+ parameters: {
5261
+ query?: never;
5262
+ header: {
5263
+ "Idempotency-Key": components["parameters"]["Idempotency-Key"];
5264
+ };
5265
+ path?: never;
5266
+ cookie?: never;
5267
+ };
5268
+ requestBody?: {
5269
+ content: {
5270
+ "application/json": components["schemas"]["WithdrawalQuoteInput"];
5271
+ };
5272
+ };
5273
+ responses: {
5274
+ /** @description OK */
5275
+ 200: {
5276
+ headers: {
5277
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5278
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5279
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5280
+ [name: string]: unknown;
5281
+ };
5282
+ content: {
5283
+ "application/json": components["schemas"]["WithdrawalQuote"];
5284
+ };
5285
+ };
5286
+ 400: components["responses"]["Error400"];
5287
+ 401: components["responses"]["Error401"];
5288
+ 403: components["responses"]["Error403"];
5289
+ 409: components["responses"]["Error409"];
5290
+ 429: components["responses"]["Error429"];
5291
+ /** @description Error response. */
5292
+ default: {
5293
+ headers: {
5294
+ [name: string]: unknown;
5295
+ };
5296
+ content: {
5297
+ "application/json": components["schemas"]["Error"];
5298
+ };
5299
+ };
5300
+ };
5301
+ };
5302
+ "withdrawal_quotes.get": {
5303
+ parameters: {
5304
+ query?: never;
5305
+ header?: never;
5306
+ path: {
5307
+ id: string;
5308
+ };
5309
+ cookie?: never;
5310
+ };
5311
+ requestBody?: never;
5312
+ responses: {
5313
+ /** @description OK */
5314
+ 200: {
5315
+ headers: {
5316
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5317
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5318
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5319
+ [name: string]: unknown;
5320
+ };
5321
+ content: {
5322
+ "application/json": components["schemas"]["WithdrawalQuote"];
5323
+ };
5324
+ };
5325
+ 400: components["responses"]["Error400"];
5326
+ 401: components["responses"]["Error401"];
5327
+ 403: components["responses"]["Error403"];
5328
+ 429: components["responses"]["Error429"];
5329
+ /** @description Error response. */
5330
+ default: {
5331
+ headers: {
5332
+ [name: string]: unknown;
5333
+ };
5334
+ content: {
5335
+ "application/json": components["schemas"]["Error"];
5336
+ };
5337
+ };
5338
+ };
5339
+ };
5340
+ "withdrawals.list": {
5341
+ parameters: {
5342
+ query?: {
5343
+ limit?: number;
5344
+ starting_after?: string;
5345
+ ending_before?: string;
5346
+ method?: "onchain" | "ach" | "wire";
5347
+ };
5348
+ header?: never;
5349
+ path?: never;
5350
+ cookie?: never;
5351
+ };
5352
+ requestBody?: never;
5353
+ responses: {
5354
+ /** @description OK */
5355
+ 200: {
5356
+ headers: {
5357
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5358
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5359
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5360
+ [name: string]: unknown;
5361
+ };
5362
+ content: {
5363
+ "application/json": components["schemas"]["WithdrawalList"];
5364
+ };
5365
+ };
5366
+ 400: components["responses"]["Error400"];
5367
+ 401: components["responses"]["Error401"];
5368
+ 403: components["responses"]["Error403"];
5369
+ 429: components["responses"]["Error429"];
5370
+ /** @description Error response. */
5371
+ default: {
5372
+ headers: {
5373
+ [name: string]: unknown;
5374
+ };
5375
+ content: {
5376
+ "application/json": components["schemas"]["Error"];
5377
+ };
5378
+ };
5379
+ };
5380
+ };
5381
+ "withdrawals.create": {
5382
+ parameters: {
5383
+ query?: never;
5384
+ header: {
5385
+ "Idempotency-Key": components["parameters"]["Idempotency-Key"];
5386
+ };
5387
+ path?: never;
5388
+ cookie?: never;
5389
+ };
5390
+ requestBody?: {
5391
+ content: {
5392
+ "application/json": components["schemas"]["WithdrawalInput"];
5393
+ };
5394
+ };
5395
+ responses: {
5396
+ /** @description OK */
5397
+ 200: {
5398
+ headers: {
5399
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5400
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5401
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5402
+ [name: string]: unknown;
5403
+ };
5404
+ content: {
5405
+ "application/json": components["schemas"]["Withdrawal"];
5406
+ };
5407
+ };
5408
+ 400: components["responses"]["Error400"];
5409
+ 401: components["responses"]["Error401"];
5410
+ 403: components["responses"]["Error403"];
5411
+ 409: components["responses"]["Error409"];
5412
+ 429: components["responses"]["Error429"];
5413
+ /** @description Error response. */
5414
+ default: {
5415
+ headers: {
5416
+ [name: string]: unknown;
5417
+ };
5418
+ content: {
5419
+ "application/json": components["schemas"]["Error"];
5420
+ };
5421
+ };
5422
+ };
5423
+ };
5424
+ "withdrawals.get": {
5425
+ parameters: {
5426
+ query?: never;
5427
+ header?: never;
5428
+ path: {
5429
+ id: string;
5430
+ };
5431
+ cookie?: never;
5432
+ };
5433
+ requestBody?: never;
5434
+ responses: {
5435
+ /** @description OK */
5436
+ 200: {
5437
+ headers: {
5438
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5439
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5440
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5441
+ [name: string]: unknown;
5442
+ };
5443
+ content: {
5444
+ "application/json": components["schemas"]["Withdrawal"];
5445
+ };
5446
+ };
5447
+ 400: components["responses"]["Error400"];
5448
+ 401: components["responses"]["Error401"];
5449
+ 403: components["responses"]["Error403"];
5450
+ 429: components["responses"]["Error429"];
5451
+ /** @description Error response. */
5452
+ default: {
5453
+ headers: {
5454
+ [name: string]: unknown;
5455
+ };
5456
+ content: {
5457
+ "application/json": components["schemas"]["Error"];
5458
+ };
5459
+ };
5460
+ };
5461
+ };
5462
+ "withdrawals.cancel": {
5463
+ parameters: {
5464
+ query?: never;
5465
+ header: {
5466
+ "Idempotency-Key": components["parameters"]["Idempotency-Key"];
5467
+ };
5468
+ path: {
5469
+ id: string;
5470
+ };
5471
+ cookie?: never;
5472
+ };
5473
+ requestBody?: never;
5474
+ responses: {
5475
+ /** @description OK */
5476
+ 200: {
5477
+ headers: {
5478
+ "X-RateLimit-Limit": components["headers"]["X-RateLimit-Limit"];
5479
+ "X-RateLimit-Remaining": components["headers"]["X-RateLimit-Remaining"];
5480
+ "X-RateLimit-Reset": components["headers"]["X-RateLimit-Reset"];
5481
+ [name: string]: unknown;
5482
+ };
5483
+ content: {
5484
+ "application/json": components["schemas"]["Withdrawal"];
5485
+ };
5486
+ };
5487
+ 400: components["responses"]["Error400"];
5488
+ 401: components["responses"]["Error401"];
5489
+ 403: components["responses"]["Error403"];
5490
+ 409: components["responses"]["Error409"];
5491
+ 429: components["responses"]["Error429"];
5492
+ /** @description Error response. */
5493
+ default: {
5494
+ headers: {
5495
+ [name: string]: unknown;
5496
+ };
5497
+ content: {
5498
+ "application/json": components["schemas"]["Error"];
5499
+ };
5500
+ };
5501
+ };
5502
+ };
5073
5503
  }