stables-mcp-server 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +52 -41
  2. package/build/index.js +3 -3
  3. package/build/index.js.map +1 -1
  4. package/build/lib/stables-client.d.ts +214 -126
  5. package/build/lib/stables-client.d.ts.map +1 -1
  6. package/build/lib/stables-client.js +54 -19
  7. package/build/lib/stables-client.js.map +1 -1
  8. package/build/tools/api-keys.d.ts.map +1 -1
  9. package/build/tools/api-keys.js +7 -3
  10. package/build/tools/api-keys.js.map +1 -1
  11. package/build/tools/customers.d.ts.map +1 -1
  12. package/build/tools/customers.js +239 -106
  13. package/build/tools/customers.js.map +1 -1
  14. package/build/tools/payment-methods.d.ts +12 -0
  15. package/build/tools/payment-methods.d.ts.map +1 -0
  16. package/build/tools/payment-methods.js +119 -0
  17. package/build/tools/payment-methods.js.map +1 -0
  18. package/build/tools/quotes.d.ts +5 -1
  19. package/build/tools/quotes.d.ts.map +1 -1
  20. package/build/tools/quotes.js +103 -59
  21. package/build/tools/quotes.js.map +1 -1
  22. package/build/tools/transfers.d.ts +4 -1
  23. package/build/tools/transfers.d.ts.map +1 -1
  24. package/build/tools/transfers.js +212 -112
  25. package/build/tools/transfers.js.map +1 -1
  26. package/build/tools/virtual-accounts.d.ts.map +1 -1
  27. package/build/tools/virtual-accounts.js +55 -69
  28. package/build/tools/virtual-accounts.js.map +1 -1
  29. package/build/tools/webhooks.d.ts.map +1 -1
  30. package/build/tools/webhooks.js +37 -19
  31. package/build/tools/webhooks.js.map +1 -1
  32. package/package.json +10 -3
  33. package/build/tools/notifications.d.ts +0 -8
  34. package/build/tools/notifications.d.ts.map +0 -1
  35. package/build/tools/notifications.js +0 -125
  36. package/build/tools/notifications.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Stables MCP Server
2
2
 
3
- An MCP (Model Context Protocol) server that exposes the Stables fiat-to-crypto API to AI agents. This allows AI assistants like Claude, ChatGPT, and others to manage customers, create quotes, execute transfers, and handle virtual accounts programmatically.
3
+ An MCP (Model Context Protocol) server that exposes the Stables fiat-to-crypto API to AI agents. This allows AI assistants like Claude, ChatGPT, Cursor, Codex, and other MCP-compatible clients to manage customers, create USDC and USDT quotes, execute approved transfers, and handle virtual accounts programmatically.
4
+
5
+ Use it to build stablecoin payment workflows for AI agents and agentic commerce: payouts, virtual account deposits, treasury movement, fiat off-ramping, and webhook reconciliation.
4
6
 
5
7
  ## What is MCP?
6
8
 
@@ -8,7 +10,7 @@ MCP (Model Context Protocol) is an open standard that provides a standardized wa
8
10
 
9
11
  ## Features
10
12
 
11
- This MCP server provides 25 tools across 7 categories:
13
+ This MCP server provides 23 tools across 7 categories:
12
14
 
13
15
  ### Customer Management
14
16
  - `create_customer` - Create individual or business customers
@@ -31,10 +33,11 @@ This MCP server provides 25 tools across 7 categories:
31
33
  - `create_virtual_account` - Create virtual bank accounts for fiat deposits
32
34
  - `list_virtual_accounts` - List virtual accounts for a customer
33
35
  - `update_virtual_account` - Update virtual account settings
34
- - `deactivate_virtual_account` - Deactivate a virtual account
35
- - `reactivate_virtual_account` - Reactivate a deactivated virtual account
36
36
  - `get_virtual_account_history` - Get activity history for a virtual account
37
37
 
38
+ ### Payment Methods
39
+ - `validate_payment_method` - Check payout details against a currency's rules before creating a quote or transfer
40
+
38
41
  ### API Keys
39
42
  - `create_api_key` - Create a new API key
40
43
  - `list_api_keys` - List all API keys
@@ -46,9 +49,6 @@ This MCP server provides 25 tools across 7 categories:
46
49
  - `list_webhooks` - List all webhook subscriptions
47
50
  - `delete_webhook` - Delete a webhook subscription
48
51
 
49
- ### Notifications
50
- - `send_verification_sms` - Send a KYC verification link to a customer via SMS (requires Twilio)
51
-
52
52
  ## Installation
53
53
 
54
54
  ```bash
@@ -69,10 +69,26 @@ The server requires the following environment variables:
69
69
  | Variable | Required | Description |
70
70
  |----------|----------|-------------|
71
71
  | `STABLES_API_KEY` | Yes | Your Stables API key |
72
- | `STABLES_API_URL` | No | API base URL (default: `https://api.sandbox.stables.money`). Must use HTTPS. |
73
- | `TWILIO_ACCOUNT_SID` | No | Twilio Account SID (required for `send_verification_sms`) |
74
- | `TWILIO_AUTH_TOKEN` | No | Twilio Auth Token (required for `send_verification_sms`) |
75
- | `TWILIO_PHONE_NUMBER` | No | Twilio phone number to send from (required for `send_verification_sms`) |
72
+ | `STABLES_API_URL` | No | API base URL. Defaults to the environment your key belongs to (see below). Must use HTTPS. |
73
+
74
+ ### Which environment you're talking to
75
+
76
+ Stables keys carry their environment: `sti_test_…` is a sandbox key, `sti_live_…`
77
+ is a production key, and the API refuses a key that arrives at the wrong
78
+ environment. So you don't have to set a URL — leave `STABLES_API_URL` unset and
79
+ the key decides:
80
+
81
+ | Your key | Where requests go |
82
+ |----------|-------------------|
83
+ | `sti_test_…` | `https://api.sandbox.stables.money` |
84
+ | `sti_live_…` | `https://api.stables.money` — **real money** |
85
+ | `sti_local_…` or anything else | production, unless you set `STABLES_API_URL` |
86
+
87
+ Setting `STABLES_API_URL` always wins, which is how you reach staging, dev or a
88
+ local deployment.
89
+
90
+ **Start with a sandbox key.** An agent holding a live key can move real money on
91
+ your behalf; see [agent safety](https://docs.stables.money/get-started/getting-started/quickstart/building-with-ai/agent-safety).
76
92
 
77
93
  ## Usage
78
94
 
@@ -97,6 +113,29 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
97
113
 
98
114
  Then restart Claude Desktop.
99
115
 
116
+ ### With Cursor, Codex, ChatGPT, or another MCP client
117
+
118
+ Use the same command and environment variables in any MCP-compatible client:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "stables": {
124
+ "command": "npx",
125
+ "args": ["stables-mcp-server"],
126
+ "env": {
127
+ "STABLES_API_KEY": "your-api-key",
128
+ "STABLES_API_URL": "https://api.sandbox.stables.money"
129
+ }
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ ## Agent safety
136
+
137
+ Stables is financial infrastructure. Agents should create quotes, prepare payment objects, and reconcile webhooks, but should require explicit human approval before creating transfers or other money movement. Check customer KYC/KYB status and entitlements before transactional actions, and treat sanctions, unsupported jurisdiction, verification, or compliance failures as hard stops.
138
+
100
139
  ### With MCP Inspector (for testing)
101
140
 
102
141
  ```bash
@@ -118,7 +157,7 @@ STABLES_API_KEY=your-api-key node build/index.js
118
157
 
119
158
  ### Creating a customer and getting a quote
120
159
 
121
- **User:** "Create a customer for john@example.com and get a quote to convert 1000 USDC to EUR"
160
+ **User:** "Create a customer for john@example.com and get a quote to convert 1000 USDT to EUR"
122
161
 
123
162
  **AI (using MCP tools):**
124
163
  1. Calls `create_customer` with email and type
@@ -179,7 +218,7 @@ stables-mcp-server/
179
218
  │ ├── virtual-accounts.ts # Virtual account tools (6)
180
219
  │ ├── api-keys.ts # API key tools (4)
181
220
  │ ├── webhooks.ts # Webhook tools (3)
182
- │ └── notifications.ts # Notification tools (1)
221
+ │ └── payment-methods.ts # Payment method validation (1)
183
222
  ├── package.json
184
223
  ├── tsconfig.json
185
224
  ├── vitest.config.ts
@@ -336,22 +375,6 @@ Update virtual account settings.
336
375
  | virtualAccountId | string | Yes | Virtual account ID |
337
376
  | depositHandlingMode | string | Yes | New deposit handling mode |
338
377
 
339
- #### deactivate_virtual_account
340
- Deactivate a virtual account to prevent new deposits.
341
-
342
- | Parameter | Type | Required | Description |
343
- |-----------|------|----------|-------------|
344
- | customerId | string | Yes | Customer ID |
345
- | virtualAccountId | string | Yes | Virtual account ID |
346
-
347
- #### reactivate_virtual_account
348
- Reactivate a previously deactivated virtual account.
349
-
350
- | Parameter | Type | Required | Description |
351
- |-----------|------|----------|-------------|
352
- | customerId | string | Yes | Customer ID |
353
- | virtualAccountId | string | Yes | Virtual account ID |
354
-
355
378
  #### get_virtual_account_history
356
379
  Get activity history for a virtual account.
357
380
 
@@ -429,18 +452,6 @@ Delete a webhook subscription.
429
452
  |-----------|------|----------|-------------|
430
453
  | webhookId | string | Yes | Webhook subscription ID |
431
454
 
432
- ### Notification Tools
433
-
434
- #### send_verification_sms
435
- Send a KYC verification link to a customer via SMS. Requires Twilio environment variables.
436
-
437
- | Parameter | Type | Required | Description |
438
- |-----------|------|----------|-------------|
439
- | customerId | string | Yes | Customer ID to send verification to |
440
- | phone | string | No | Override phone number (uses customer's phone if not provided) |
441
- | botName | string | No | Name of the assistant sending the message |
442
- | verificationLinkTtlSecs | number | No | Verification link expiry in seconds (default: 1800) |
443
-
444
455
  ## Security
445
456
 
446
457
  - API keys are only read from environment variables
package/build/index.js CHANGED
@@ -32,7 +32,7 @@ import { registerTransferTools } from "./tools/transfers.js";
32
32
  import { registerVirtualAccountTools } from "./tools/virtual-accounts.js";
33
33
  import { registerApiKeyTools } from "./tools/api-keys.js";
34
34
  import { registerWebhookTools } from "./tools/webhooks.js";
35
- import { registerNotificationTools } from "./tools/notifications.js";
35
+ import { registerPaymentMethodTools } from "./tools/payment-methods.js";
36
36
  // Validate environment
37
37
  const apiKey = process.env.STABLES_API_KEY;
38
38
  if (!apiKey) {
@@ -43,7 +43,7 @@ if (!apiKey) {
43
43
  // Create the MCP server
44
44
  const server = new McpServer({
45
45
  name: "stables-mcp-server",
46
- version: "1.2.0",
46
+ version: "2.0.0",
47
47
  description: "Stables fiat-to-crypto API for AI agents - manage customers, quotes, transfers, and virtual accounts",
48
48
  });
49
49
  // Create the Stables API client
@@ -55,7 +55,7 @@ registerTransferTools(server, stablesClient);
55
55
  registerVirtualAccountTools(server, stablesClient);
56
56
  registerApiKeyTools(server, stablesClient);
57
57
  registerWebhookTools(server, stablesClient);
58
- registerNotificationTools(server, stablesClient);
58
+ registerPaymentMethodTools(server, stablesClient);
59
59
  // Start the server with STDIO transport
60
60
  async function main() {
61
61
  const transport = new StdioServerTransport();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE,uBAAuB;AACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,wBAAwB;AACxB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;IAChB,WAAW,EAAE,sGAAsG;CACpH,CAAC,CAAC;AAEH,gCAAgC;AAChC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAC;AAE5C,qBAAqB;AACrB,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,kBAAkB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC1C,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,2BAA2B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACnD,mBAAmB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3C,oBAAoB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5C,yBAAyB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAEjD,wCAAwC;AACxC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAExE,uBAAuB;AACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,wBAAwB;AACxB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,OAAO;IAChB,WAAW,EACT,sGAAsG;CACzG,CAAC,CAAC;AAEH,gCAAgC;AAChC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAC;AAE5C,qBAAqB;AACrB,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,kBAAkB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC1C,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC7C,2BAA2B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACnD,mBAAmB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3C,oBAAoB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAC5C,0BAA0B,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAElD,wCAAwC;AACxC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -8,92 +8,117 @@ export declare class StablesApiError extends Error {
8
8
  readonly errorBody?: unknown | undefined;
9
9
  constructor(message: string, statusCode: number, endpoint: string, errorBody?: unknown | undefined);
10
10
  }
11
- export type CustomerType = "CUSTOMER_TYPE_INDIVIDUAL" | "CUSTOMER_TYPE_BUSINESS";
12
- export type VerificationStatus = "VERIFICATION_IN_PROGRESS" | "VERIFICATION_APPROVED" | "VERIFICATION_REJECTED";
13
- export type VerificationLevel = "KYC_LEVEL_0" | "KYC_LEVEL_1" | "KYC_LEVEL_2" | "BASE_BUSINESS" | "INDIVIDUAL_BASE" | "BUSINESS_BASE" | "INDIVIDUAL_ENHANCED";
11
+ export type CustomerType = "individual" | "business";
12
+ export type VerificationStatus = "in_progress" | "approved" | "rejected" | "not_started";
13
+ export type VerificationLevel = "individual_base" | "individual_enhanced" | "business_base" | "base_business";
14
14
  export interface VerificationLevelResponse {
15
15
  level: VerificationLevel;
16
16
  status: VerificationStatus;
17
+ sub_status?: string[];
18
+ details?: unknown[];
17
19
  }
20
+ /** The five feature entitlements the API accepts. */
21
+ export type EntitlementId = "base_payout" | "virtual_account" | "eur_virtual_account" | "usd_virtual_account" | "aed_local";
18
22
  export interface Entitlement {
19
23
  name: string;
20
- status: "ENTITLEMENT_STATUS_SUBMITTED" | "ENTITLEMENT_STATUS_IN_PROGRESS" | "ENTITLEMENT_STATUS_APPROVED" | "ENTITLEMENT_STATUS_REJECTED";
24
+ status: "submitted" | "pending" | "in_progress" | "approved" | "rejected";
21
25
  }
22
26
  export interface CustomerAddress {
23
27
  line1: string;
24
28
  line2?: string;
25
29
  city: string;
26
30
  state?: string;
27
- postalCode?: string;
31
+ postal_code?: string;
28
32
  country: string;
29
33
  }
30
34
  export interface Customer {
31
- customerId: string;
32
- externalCustomerId: string;
33
- customerType: CustomerType;
35
+ customer_id: string;
36
+ external_customer_id?: string;
37
+ customer_type: CustomerType;
34
38
  email: string;
35
39
  phone?: string;
36
- firstName?: string;
37
- lastName?: string;
38
- companyName?: string;
40
+ first_name?: string;
41
+ last_name?: string;
42
+ company_name?: string;
43
+ status?: string;
44
+ compliance_lock?: boolean;
39
45
  entitlements?: Entitlement[];
40
- createdAt: string;
41
- updatedAt: string;
42
- verificationLevels: VerificationLevelResponse[];
46
+ created_at: string;
47
+ updated_at: string;
48
+ verification_levels?: VerificationLevelResponse[];
43
49
  metadata?: Record<string, string>;
44
50
  }
45
51
  export interface CreateIndividualCustomerRequest {
46
- externalCustomerId: string;
47
- customerType: "CUSTOMER_TYPE_INDIVIDUAL";
48
- email?: string;
49
- firstName?: string;
50
- lastName?: string;
51
- middleName?: string;
52
+ customer_type: "individual";
53
+ /** Required — the API rejects a customer without one. */
54
+ email: string;
55
+ external_customer_id?: string;
56
+ first_name?: string;
57
+ last_name?: string;
58
+ middle_name?: string;
52
59
  phone?: string;
53
60
  dob?: string;
54
61
  nationality?: string;
62
+ tax_id_number?: string;
55
63
  address?: CustomerAddress;
56
- entitlements?: string[];
64
+ entitlements?: EntitlementId[];
57
65
  metadata?: Record<string, string>;
58
66
  }
59
67
  export interface CreateBusinessCustomerRequest {
60
- externalCustomerId: string;
61
- customerType: "CUSTOMER_TYPE_BUSINESS";
62
- email?: string;
68
+ customer_type: "business";
69
+ email: string;
70
+ company_name: string;
71
+ external_customer_id?: string;
63
72
  phone?: string;
64
- companyName: string;
65
73
  country?: string;
66
- registrationNumber?: string;
67
- legalAddress?: CustomerAddress;
68
- incorporatedOn?: string;
74
+ registration_number?: string;
75
+ legal_address?: CustomerAddress;
76
+ postal_address?: CustomerAddress;
77
+ incorporated_on?: string;
69
78
  type?: string;
70
- taxId?: string;
71
- registrationLocation?: string;
79
+ tax_id?: string;
80
+ registration_location?: string;
72
81
  website?: string;
73
- postalAddress?: CustomerAddress;
74
- alternativeNames?: string[];
75
- describeBusiness?: string;
76
- conductMoneyServices?: boolean;
77
- describeMoneyServices?: string;
78
- describeComplianceControls?: string;
79
- accountPurpose?: string;
80
- accountPurposeOther?: string;
81
- isYourBusinessADao?: boolean;
82
- industrySelection?: string;
83
- mainSourceOfFunds?: string;
84
- sourceOfFunds?: string;
85
- sourceOfFundsDescription?: string;
86
- expectedAnnualRevenue?: string;
87
- expectedMonthlyPayments?: string;
88
- doesYourBusinessEngageInHighRiskActivities?: "yes" | "no";
89
- highRiskActivities?: string[];
90
- operateInProhibitedCountry?: boolean;
91
- acceptTerms?: boolean;
92
- howDidYouComeAcrossStables?: string;
93
- entitlements?: string[];
82
+ alternative_names?: string[];
83
+ describe_business?: string;
84
+ conduct_money_services?: boolean;
85
+ describe_money_services?: string;
86
+ describe_compliance_controls?: string;
87
+ account_purpose?: string;
88
+ account_purpose_other?: string;
89
+ is_your_business_a_dao?: boolean;
90
+ industry_selection?: string;
91
+ main_source_of_funds?: string;
92
+ source_of_funds?: string;
93
+ source_of_funds_description?: string;
94
+ expected_annual_revenue?: string;
95
+ expected_monthly_payments?: string;
96
+ does_your_business_engage_in_high_risk_activities?: "yes" | "no";
97
+ high_risk_activities?: string[];
98
+ operate_in_prohibited_country?: boolean;
99
+ accept_terms?: boolean;
100
+ how_did_you_come_across_stables?: string;
101
+ entitlements?: EntitlementId[];
94
102
  metadata?: Record<string, string>;
95
103
  }
96
104
  export type CreateCustomerRequest = CreateIndividualCustomerRequest | CreateBusinessCustomerRequest;
105
+ /**
106
+ * Every field optional, so an unknown key is not an error — it is simply
107
+ * dropped. Sending camelCase here parsed to an empty object and returned 200
108
+ * having changed nothing.
109
+ */
110
+ export interface UpdateCustomerRequest {
111
+ email?: string;
112
+ phone?: string;
113
+ first_name?: string;
114
+ last_name?: string;
115
+ middle_name?: string;
116
+ dob?: string;
117
+ nationality?: string;
118
+ company_name?: string;
119
+ entitlements?: EntitlementId[];
120
+ metadata?: Record<string, string>;
121
+ }
97
122
  export interface ListCustomersResponse {
98
123
  customers: Customer[];
99
124
  }
@@ -108,64 +133,105 @@ export interface GenerateVerificationLinkRequest {
108
133
  redirect?: VerificationRedirect;
109
134
  }
110
135
  export interface GenerateVerificationLinkResponse {
111
- customerId: string;
112
- kycLink: string;
113
- }
114
- export type TransferType = "TRANSFER_TYPE_OFFRAMP" | "TRANSFER_TYPE_ONRAMP";
115
- export type TransferStatus = "CREATED" | "COMPLIANCE_HOLD" | "AWAITING_FUNDS_COLLECTION" | "FUNDS_COLLECTED" | "PAYMENT_SUBMITTED" | "PAYMENT_PROCESSED" | "COMPLETED" | "FAILED" | "CANCELLED" | "EXPIRED";
116
- export interface BankCodes {
117
- swiftCode?: string;
118
- bicCode?: string;
119
- ifscCode?: string;
120
- abaCode?: string;
121
- sortCode?: string;
122
- branchCode?: string;
123
- bsbCode?: string;
124
- bankCode?: string;
125
- cnaps?: string;
136
+ customer_id: string;
137
+ kyc_link: string;
126
138
  }
127
- export interface BankTransferDetails {
128
- accountHolderName: string;
129
- accountNumber?: string;
130
- iban?: string;
131
- bankName: string;
132
- bankCountry: string;
139
+ export type TransferType = "offramp" | "onramp";
140
+ export type TransferStatus = "created" | "compliance_hold" | "awaiting_funds_collection" | "funds_collected" | "in_progress" | "payment_submitted" | "payment_processed" | "completed" | "failed" | "cancelled" | "expired" | "unknown";
141
+ /** ISO 3166-1 alpha-2, lowercase, per AddressApiSchema. */
142
+ export interface BeneficiaryAddress {
143
+ street: string;
144
+ city: string;
145
+ state: string;
146
+ postal_code: string;
147
+ country: string;
148
+ }
149
+ /**
150
+ * Bank payout destination. Bank codes are flat here — the nested `bankCodes`
151
+ * object the API used to take is gone.
152
+ */
153
+ export interface BankTransferDestination {
154
+ type: "bank";
155
+ account_holder_name: string;
156
+ bank_name: string;
157
+ bank_country: string;
133
158
  currency: string;
134
- accountType?: "savings" | "checking" | "payment";
135
- branchName?: string;
136
- bankCodes?: BankCodes;
159
+ /**
160
+ * Required for payouts in AED, CAD, EUR, GBP, MXN and USD, together with
161
+ * `address`, and `date_of_birth` when this is "individual".
162
+ */
163
+ recipient_type?: "individual" | "business";
164
+ date_of_birth?: string;
165
+ address?: BeneficiaryAddress;
166
+ account_number?: string;
167
+ iban?: string;
168
+ pay_id?: string;
169
+ pay_id_type?: "email" | "phone" | "abn" | "org_id";
170
+ account_type?: "savings" | "checking" | "payment";
171
+ branch_name?: string;
172
+ swift_code?: string;
173
+ bic_code?: string;
174
+ ifsc_code?: string;
175
+ aba_code?: string;
176
+ sort_code?: string;
177
+ branch_code?: string;
178
+ bsb_code?: string;
179
+ bank_code?: string;
180
+ cnaps?: string;
181
+ name_in_local_language?: string;
182
+ national_identification_number?: string;
183
+ }
184
+ export interface PaymentMethodValidationError {
185
+ field?: string;
186
+ message: string;
187
+ code: "UNSUPPORTED_CURRENCY" | "UNSUPPORTED_PAYMENT_METHOD" | "INVALID_FIELDS";
137
188
  }
138
- export interface PaymentMethod {
139
- bankTransfer: BankTransferDetails;
189
+ export interface ValidatePaymentMethodResponse {
190
+ valid: boolean;
191
+ errors?: PaymentMethodValidationError[];
140
192
  }
141
- export interface CollectionInstructions {
142
- walletAddress: string;
193
+ export interface CryptoTransferDestination {
194
+ type: "crypto";
143
195
  currency: string;
144
196
  network: string;
145
- amount: string;
197
+ address: string;
198
+ }
199
+ export type TransferDestination = BankTransferDestination | CryptoTransferDestination;
200
+ /** Where the customer sends funds for an offramp. Was `collectionInstructions`. */
201
+ export interface SourceDepositInstructions {
202
+ wallet_address?: string;
203
+ currency?: string;
204
+ network?: string;
205
+ amount?: string;
206
+ [key: string]: unknown;
146
207
  }
147
208
  export interface Transfer {
148
209
  id: string;
149
- tenantId: string;
150
- customerId: string;
151
- quoteId: string;
210
+ tenant_id: string;
211
+ customer_id: string;
212
+ quote_id: string;
152
213
  type: TransferType;
153
214
  status: TransferStatus;
154
- createdAt: string;
155
- updatedAt: string;
156
- collectionInstructions?: CollectionInstructions;
215
+ origin?: "api" | "otc";
216
+ created_at: string;
217
+ updated_at: string;
218
+ source_deposit_instructions?: SourceDepositInstructions;
219
+ destination?: TransferDestination;
220
+ fees?: Record<string, unknown>;
221
+ exchange_rate?: string;
157
222
  metadata?: Record<string, string>;
158
223
  }
159
224
  export interface CreateTransferRequest {
160
- customerId: string;
161
- quoteId: string;
162
- paymentMethod?: PaymentMethod;
225
+ customer_id: string;
226
+ quote_id: string;
227
+ destination: TransferDestination;
228
+ purpose_code?: string;
163
229
  metadata?: Record<string, string>;
164
230
  }
165
231
  export interface ListTransfersResponse {
166
232
  transfers: Transfer[];
167
233
  page: {
168
- nextPageToken: string;
234
+ next_page_token: string;
169
235
  total: number;
170
236
  };
171
237
  }
@@ -235,56 +301,62 @@ export interface VirtualAccountHistoryEvent {
235
301
  deposit_id?: string;
236
302
  created_at: string;
237
303
  }
238
- export type QuoteStatus = "QUOTE_STATUS_ACTIVE" | "QUOTE_STATUS_EXPIRED" | "QUOTE_STATUS_USED" | "QUOTE_STATUS_CANCELLED";
239
- export type PaymentMethodType = "SWIFT" | "LOCAL";
240
- export type QuoteNetwork = "ethereum" | "polygon";
304
+ export type QuoteStatus = "active" | "expired" | "used" | "cancelled" | "preview";
305
+ /**
306
+ * Live blockchain networks. The client used to allow only ethereum and polygon,
307
+ * which blocked six networks the platform supports.
308
+ */
309
+ export type QuoteNetwork = "arbitrum" | "avalanche" | "base" | "ethereum" | "optimism" | "polygon" | "solana" | "tron";
241
310
  export interface CurrencyAmount {
242
311
  currency: string;
243
312
  amount: string;
244
313
  network?: string;
245
314
  }
246
315
  export interface FeeBreakdown {
247
- fxFee: CurrencyAmount;
248
- integratorFee: CurrencyAmount;
249
- platformFee: CurrencyAmount;
250
- paymentMethodFee: CurrencyAmount;
251
- networkFee?: CurrencyAmount;
252
- totalFee: CurrencyAmount;
316
+ fx_fee?: CurrencyAmount;
317
+ integrator_fee?: CurrencyAmount;
318
+ platform_fee?: CurrencyAmount;
319
+ payment_method_fee?: CurrencyAmount;
320
+ network_fee?: CurrencyAmount;
321
+ total_fee: CurrencyAmount;
253
322
  }
254
323
  export interface Quote {
255
- quoteId: string;
256
- from: CurrencyAmount;
257
- to: {
258
- currency: string;
259
- amount: string;
260
- network?: string;
261
- paymentMethodType: PaymentMethodType;
262
- };
324
+ quote_id: string;
325
+ source: CurrencyAmount;
326
+ destination: CurrencyAmount;
263
327
  fees: FeeBreakdown;
264
- exchangeRate: number;
265
- expiresAt: string;
266
- createdAt: string;
328
+ exchange_rate: number;
329
+ expires_at: string;
330
+ created_at: string;
267
331
  status: QuoteStatus;
268
332
  metadata?: Record<string, string>;
269
333
  }
270
334
  export interface CreateQuoteRequest {
271
- customerId?: string;
272
- from: {
335
+ source: {
273
336
  currency: string;
274
- network: QuoteNetwork;
337
+ /** Required for offramp (crypto source); omitted for onramp (fiat source). */
338
+ network?: string;
275
339
  amount: string;
276
340
  };
277
- to: {
341
+ destination: {
278
342
  currency: string;
279
- country: string;
280
- network?: QuoteNetwork;
281
- paymentMethodType: PaymentMethodType;
343
+ /** Required for offramp. */
344
+ country?: string;
345
+ /** Offramp: payment network (swift/bank). Onramp: blockchain network. */
346
+ network?: string;
347
+ /** Required for onramp. */
348
+ address?: string;
282
349
  };
350
+ /** Price without persisting the quote. Returns status "preview". */
351
+ preview?: boolean;
283
352
  metadata?: Record<string, string>;
284
353
  }
285
- export interface CreateQuoteResponse {
286
- quote: Quote;
287
- }
354
+ /**
355
+ * The quote endpoints return the quote directly. They used to wrap it in
356
+ * `{ quote }`, and reading the wrapper gave undefined and then a TypeError on
357
+ * the first field access.
358
+ */
359
+ export type CreateQuoteResponse = Quote;
288
360
  export interface ApiKey {
289
361
  apiKeyId: string;
290
362
  tenantId: string;
@@ -331,7 +403,7 @@ export declare class StablesApiClient {
331
403
  listCustomers(): Promise<ListCustomersResponse>;
332
404
  getCustomer(customerId: string): Promise<Customer>;
333
405
  createCustomer(data: CreateCustomerRequest): Promise<Customer>;
334
- updateCustomer(customerId: string, data: Record<string, unknown>): Promise<Customer>;
406
+ updateCustomer(customerId: string, data: UpdateCustomerRequest): Promise<Customer>;
335
407
  updateMetadata(customerId: string, metadata: Record<string, string>): Promise<void>;
336
408
  generateVerificationLink(customerId: string, options?: GenerateVerificationLinkRequest): Promise<GenerateVerificationLinkResponse>;
337
409
  listAllVirtualAccounts(params?: {
@@ -346,8 +418,6 @@ export declare class StablesApiClient {
346
418
  updateVirtualAccount(customerId: string, virtualAccountId: string, data: {
347
419
  deposit_handling_mode?: DepositHandlingMode;
348
420
  }): Promise<VirtualAccount>;
349
- deactivateVirtualAccount(customerId: string, virtualAccountId: string): Promise<VirtualAccount>;
350
- reactivateVirtualAccount(customerId: string, virtualAccountId: string): Promise<VirtualAccount>;
351
421
  getVirtualAccountHistory(customerId: string, virtualAccountId: string, params?: {
352
422
  limit?: number;
353
423
  depositId?: string;
@@ -367,9 +437,7 @@ export declare class StablesApiClient {
367
437
  getTransfer(transferId: string): Promise<Transfer>;
368
438
  createTransfer(data: CreateTransferRequest): Promise<Transfer>;
369
439
  createQuote(data: CreateQuoteRequest): Promise<CreateQuoteResponse>;
370
- getQuote(quoteId: string): Promise<{
371
- quote: Quote;
372
- }>;
440
+ getQuote(quoteId: string): Promise<Quote>;
373
441
  listApiKeys(params?: {
374
442
  pageSize?: number;
375
443
  pageToken?: string;
@@ -381,6 +449,12 @@ export declare class StablesApiClient {
381
449
  apiKey: ApiKey;
382
450
  }>;
383
451
  revokeApiKey(apiKeyId: string): Promise<Record<string, never>>;
452
+ /**
453
+ * Check payout details against the per-currency rules without creating
454
+ * anything. Always answers 200 — the outcome is in the body — so an agent can
455
+ * discover what a corridor demands before spending a short-lived quote.
456
+ */
457
+ validatePaymentMethod(network: string, destination: BankTransferDestination): Promise<ValidatePaymentMethodResponse>;
384
458
  listWebhooks(): Promise<{
385
459
  subscriptions: WebhookSubscription[];
386
460
  }>;
@@ -389,5 +463,19 @@ export declare class StablesApiClient {
389
463
  }>;
390
464
  deleteWebhook(subscriptionId: string): Promise<Record<string, never>>;
391
465
  }
466
+ /**
467
+ * Which environment a key belongs to, read from the key itself.
468
+ *
469
+ * Stables keys are `sti_<env>_<prefix>_<secret>` with env one of local, test or
470
+ * live, and the api service refuses a key whose segment does not match the
471
+ * deployment it arrives at. So the key already decides the environment, and a
472
+ * default base URL is only ever a guess at something we can read.
473
+ *
474
+ * Guessing had a cost in both directions: the code defaulted to production while
475
+ * the README promised sandbox, so a test key with no STABLES_API_URL set failed
476
+ * authentication against production and read as "my key is invalid" when the key
477
+ * was fine.
478
+ */
479
+ export declare function apiUrlForKey(apiKey: string): string | null;
392
480
  export declare function createStablesClient(): StablesApiClient;
393
481
  //# sourceMappingURL=stables-client.d.ts.map