From e461c9d3bae3a6357b7646ffe3eac6bad87814a9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sun, 23 Aug 2026 23:45:23 +0000 Subject: [PATCH 1/9] docs: deprecate /transfer-in and /transfer-out in favor of /quotes Same-currency transfers now route through the quote endpoint on the backend, so the docs should point integrators there. API reference: - Mark POST /transfer-in and POST /transfer-out `deprecated: true` and document the field-by-field migration to POST /quotes in each description. - Invert the note on POST /quotes that sent same-currency traffic to the transfer endpoints, and add a same-currency request example. - Update the Same-Currency/Cross-Currency tag descriptions to match. Guides: replace every transfer-in/transfer-out example with the equivalent POST /quotes call using `immediatelyExecute: true`, and update the surrounding prose and response payloads (a quote carries `transactionId` rather than being a transaction itself). Also mark the endpoints deprecated in the repo's agent-facing reference docs so tooling stops recommending them, and add a changelog entry. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .claude/skills/grid-api/SKILL.md | 34 +++++--- .../skills/grid-api/references/endpoints.md | 6 +- components/grid-visualizer/CLAUDE.md | 6 +- mintlify/api-reference/sandbox-testing.mdx | 6 +- mintlify/changelog.mdx | 19 +++++ mintlify/openapi.yaml | 75 +++++++++++++++-- .../payment-flow/send-payment.mdx | 82 ++++++++++--------- .../platform-tools/sandbox-testing.mdx | 30 ++++--- .../core-concepts/account-model.mdx | 32 ++++---- .../core-concepts/quote-system.mdx | 37 ++++----- .../core-concepts/transaction-lifecycle.mdx | 46 +++++++---- .../conversion-flows/self-custody-wallets.mdx | 35 ++++---- .../onboarding/implementation-overview.mdx | 2 +- .../platform-tools/sandbox-testing.mdx | 2 +- mintlify/snippets/error-handling.mdx | 4 +- mintlify/snippets/sending/same-currency.mdx | 55 ++++++++----- mintlify/snippets/terminology.mdx | 5 +- openapi.yaml | 75 +++++++++++++++-- openapi/openapi.yaml | 10 ++- openapi/paths/quotes/quotes.yaml | 28 +++++-- openapi/paths/transfers/transfer_in.yaml | 21 ++++- openapi/paths/transfers/transfer_out.yaml | 22 ++++- 22 files changed, 437 insertions(+), 195 deletions(-) diff --git a/.claude/skills/grid-api/SKILL.md b/.claude/skills/grid-api/SKILL.md index 7036f5248..26d87d55e 100644 --- a/.claude/skills/grid-api/SKILL.md +++ b/.claude/skills/grid-api/SKILL.md @@ -420,29 +420,36 @@ values `SMS_OTP` (default) or `PASSKEY`. ### Same-Currency Transfers +Use `/quotes` with `"immediatelyExecute": true`. The `/transfer-in` and +`/transfer-out` endpoints are deprecated. + ```bash -# Transfer in (external → internal, same currency) +# External → internal, same currency (source must support pull, e.g. ACH pull) curl -s -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -X POST -H "Content-Type: application/json" \ -d '{ - "source": {"accountId": ""}, - "destination": {"accountId": ""}, - "amount": 10000 + "source": {"sourceType": "ACCOUNT", "accountId": ""}, + "destination": {"destinationType": "ACCOUNT", "accountId": ""}, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 10000, + "immediatelyExecute": true }' \ - "$GRID_BASE_URL/transfer-in" | jq . + "$GRID_BASE_URL/quotes" | jq . -# Transfer out (internal → external, same currency) +# Internal → external, same currency # Optional: top-level "remittanceInformation" (memo, max 80 chars) and a "paymentRail" # inside the destination to pick a specific supported rail. curl -s -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -X POST -H "Content-Type: application/json" \ -d '{ - "source": {"accountId": ""}, - "destination": {"accountId": "", "paymentRail": ""}, - "amount": 10000, + "source": {"sourceType": "ACCOUNT", "accountId": ""}, + "destination": {"destinationType": "ACCOUNT", "accountId": "", "paymentRail": ""}, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 10000, + "immediatelyExecute": true, "remittanceInformation": "Invoice 1234" }' \ - "$GRID_BASE_URL/transfer-out" | jq . + "$GRID_BASE_URL/quotes" | jq . ``` ### Transactions @@ -535,11 +542,12 @@ Use `sourceType: "REALTIME_FUNDING"` with customer ID and currency. Only works w ### 3. Same-Currency Transfers -Direct transfers between accounts without currency conversion. No quote needed. +Transfers between accounts without currency conversion. These go through `/quotes` +like any other transfer; set `immediatelyExecute` to create and execute in one call. ``` -External Account (USD) → Internal Account (USD) [transfer-in] -Internal Account (USD) → External Account (USD) [transfer-out] +External Account (USD) → Internal Account (USD) +Internal Account (USD) → External Account (USD) ``` ## Interactive Payment Workflows diff --git a/.claude/skills/grid-api/references/endpoints.md b/.claude/skills/grid-api/references/endpoints.md index e7550a096..3089212e9 100644 --- a/.claude/skills/grid-api/references/endpoints.md +++ b/.claude/skills/grid-api/references/endpoints.md @@ -118,10 +118,12 @@ Internal accounts are auto-created when customers are created based on platform ## Same-Currency Transfers +Deprecated — use `POST /quotes` with `immediatelyExecute` instead. + | Method | Endpoint | Description | |--------|----------|-------------| -| POST | `/transfer-in` | Create a transfer-in request (external to internal) | -| POST | `/transfer-out` | Create a transfer-out request (internal to external) | +| POST | `/transfer-in` | **Deprecated.** Create a transfer-in request (external to internal) | +| POST | `/transfer-out` | **Deprecated.** Create a transfer-out request (internal to external) | ## Receiver Lookup diff --git a/components/grid-visualizer/CLAUDE.md b/components/grid-visualizer/CLAUDE.md index 51f3eafc8..3a48ae5ae 100644 --- a/components/grid-visualizer/CLAUDE.md +++ b/components/grid-visualizer/CLAUDE.md @@ -204,10 +204,10 @@ Stablecoins: `SOLANA_WALLET` (USDC), `POLYGON_WALLET` (USDC), `BASE_WALLET` (USD - `POST /customers/external-accounts` — Register external account - `GET /customers/internal-accounts` — List customer internal accounts - `GET /platform/internal-accounts` — List platform internal accounts -- `POST /quotes` — Create cross-currency transfer quote +- `POST /quotes` — Create a transfer quote (same-currency and cross-currency) - `POST /quotes/{quoteId}/execute` — Execute quote -- `POST /transfer-out` — Same-currency internal → external -- `POST /transfer-in` — Same-currency external → internal +- `POST /transfer-out` — **Deprecated.** Same-currency internal → external; use `/quotes` +- `POST /transfer-in` — **Deprecated.** Same-currency external → internal; use `/quotes` ### Quote Request Shape diff --git a/mintlify/api-reference/sandbox-testing.mdx b/mintlify/api-reference/sandbox-testing.mdx index 83965cbc5..dc2058aa3 100644 --- a/mintlify/api-reference/sandbox-testing.mdx +++ b/mintlify/api-reference/sandbox-testing.mdx @@ -33,7 +33,7 @@ In production, internal accounts are funded by sending a bank transfer to the ac ### Transfer in from an external account -Use the `/transfer-in` endpoint to pull funds from an external account into an internal account. The external account's number suffix determines the outcome: +Use the `/quotes` endpoint with `immediatelyExecute` set to `true` to pull funds from an external account into an internal account. The external account's number suffix determines the outcome: @@ -48,7 +48,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-account -d '{ "amount": 100000 }' ``` -## Creating quotes (cross-currency transfers) +## Creating quotes @@ -77,7 +77,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/send \ ## Transferring out funds -Use the `/transfer-out` endpoint to push funds from an internal account to an external account in the same currency. The external account's number suffix controls the outcome using the same patterns as [transfer in](#transfer-in-from-an-external-account). +Use the `/quotes` endpoint with `immediatelyExecute` set to `true` to push funds from an internal account to an external account in the same currency. The external account's number suffix controls the outcome using the same patterns as [transfer in](#transfer-in-from-an-external-account). ## Sending to a UMA address diff --git a/mintlify/changelog.mdx b/mintlify/changelog.mdx index ea908e117..c5b5f0661 100644 --- a/mintlify/changelog.mdx +++ b/mintlify/changelog.mdx @@ -11,6 +11,25 @@ changes and roadmap, [book a live demo](https://www.lightspark.com/contact) or +## `/transfer-in` and `/transfer-out` are deprecated + +Same-currency transfers now go through the quote endpoint, so one integration covers +same-currency and cross-currency alike. + +- Use `POST /quotes` with `immediatelyExecute: true` to create and execute a + same-currency transfer in a single request. +- `amount` becomes `lockedCurrencyAmount` with `lockedCurrencySide: "SENDING"`; source + and destination gain `sourceType: "ACCOUNT"` and `destinationType: "ACCOUNT"`. + `remittanceInformation`, `purposeOfPayment`, and the destination `paymentRail` carry + over unchanged. +- The response is a `Quote` rather than a `Transaction`—read `transactionId` from it to + track the resulting transaction. +- `POST /transfer-in` and `POST /transfer-out` continue to work with unchanged request + and response shapes. + +See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#same-currency-transfers) +for the updated flow. + ## Assess your own fees on every transaction Charge your customers a platform fee and keep the margin—Grid collects it for you and diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 51644742f..d20405c37 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -36,9 +36,9 @@ tags: - name: VASPs description: Directory of Virtual Asset Service Providers (exchanges and other custodial platforms) recognized for counterparty declarations. - name: Same-Currency Transfers - description: Endpoints for transferring funds between internal and external accounts with the same currency + description: Deprecated endpoints for transferring funds between internal and external accounts with the same currency. Use the quote endpoints under Cross-Currency Transfers instead, which now serve same-currency transfers as well. - name: Cross-Currency Transfers - description: Endpoints for creating and confirming quotes for cross-currency transfers + description: Endpoints for creating and confirming quotes for transfers, both same-currency and cross-currency - name: Transactions description: Endpoints for retrieving transaction information - name: Webhooks @@ -3593,8 +3593,29 @@ paths: /transfer-in: post: summary: Create a transfer-in request + deprecated: true description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an external account source and an internal account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + + Transfer funds from an external account to an internal account for a specific customer. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). + Otherwise, use the paymentInstructions on the internal account to deposit funds. operationId: createTransferIn tags: - Same-Currency Transfers @@ -3658,7 +3679,27 @@ paths: /transfer-out: post: summary: Create a transfer-out request + deprecated: true description: | + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an internal account source and an external account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs and `destination.paymentRail` are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - `remittanceInformation` and `purposeOfPayment` carry over unchanged + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + Transfer funds from an internal account to an external account for a specific customer. operationId: createTransferOut tags: @@ -3924,12 +3965,12 @@ paths: post: summary: Create a transfer quote description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. + Generate a quote for a transfer between any combination of accounts + and UMA addresses. This endpoint handles same-currency and cross-currency + transfers alike, and provides the necessary instructions to execute the transfer. **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to Account**: Transfer between internal/external accounts, with or without currency exchange. - **Account to UMA**: Transfer from an internal account to an UMA address. - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. @@ -3938,8 +3979,9 @@ paths: - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. + **Same-currency transfers:** Use this endpoint for same-currency transfers too. + Set `immediatelyExecute: true` to create and execute in a single request. The + older `/transfer-in` and `/transfer-out` endpoints are deprecated. Requires a token with the `TRANSACT` permission; `VIEW` alone is not sufficient. A quote is the instrument a later execute draws on, and @@ -3965,6 +4007,21 @@ paths: schema: $ref: '#/components/schemas/QuoteRequest' examples: + sameCurrencyAccountToAccount: + summary: Same-Currency Account to Account Transfer (immediate execution) + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + lockedCurrencySide: SENDING + lockedCurrencyAmount: 12550 + immediatelyExecute: true + remittanceInformation: INV-12345 + description: Same-currency payout, no exchange required. accountToAccount: summary: Account to Account Transfer value: diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index bfcf16a1d..c3e7948f5 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -39,7 +39,11 @@ Before sending payments, ensure you have: ## Same-Currency Transfers -Use the `/transfer-out` endpoint when sending funds in the same currency (no exchange rate needed). This is the simplest and fastest option for domestic transfers. +Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. + + + The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work and their request and response shapes are unchanged, but new integrations should use `/quotes`. + ### When to use same-currency transfers @@ -62,22 +66,26 @@ Note the `id` fields from both the internal and external accounts you want to us - - Create the transfer by specifying the source and destination accounts: + + Create the quote by specifying the source and destination accounts. Set `immediatelyExecute` to `true` to move the funds in the same request: ```bash cURL -curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \ +curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -H 'Content-Type: application/json' \ -d '{ "source": { + "sourceType": "ACCOUNT", "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, "destination": { + "destinationType": "ACCOUNT", "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", "paymentRail": "ACH" }, - "amount": 12550, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 12550, + "immediatelyExecute": true, "remittanceInformation": "INV-12345" }' ``` @@ -91,57 +99,57 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \ - `purposeOfPayment` is optional and accepts the same values as on a quote. Some destinations - require it, and some rails carry it on the payment itself. + `purposeOfPayment` is optional. Some destinations require it, and some rails carry it on + the payment itself. ```json Success (201 Created) { - "id": "Transaction:019542f5-b3e7-1d02-0000-000000000015", - "status": "PENDING", - "type": "OUTGOING", - "direction": "DEBIT", + "id": "Quote:019542f5-b3e7-1d02-0000-000000000006", + "status": "PROCESSING", + "createdAt": "2025-10-03T15:00:00Z", + "expiresAt": "2025-10-03T15:05:00Z", "source": { - "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", - "currency": "USD" + "sourceType": "ACCOUNT", + "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, "destination": { - "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", - "currency": "USD" + "destinationType": "ACCOUNT", + "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" }, - "sentAmount": { - "amount": 12550, - "currency": { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "decimals": 2 - } + "sendingCurrency": { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "decimals": 2 }, - "receivedAmount": { - "amount": 12550, - "currency": { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "decimals": 2 - } + "receivingCurrency": { + "code": "USD", + "name": "United States Dollar", + "symbol": "$", + "decimals": 2 }, - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "platformCustomerId": "customer_12345", - "createdAt": "2025-10-03T15:00:00Z", - "settledAt": null + "totalSendingAmount": 12550, + "totalReceivingAmount": 12550, + "exchangeRate": 1, + "feesIncluded": 0, + "platformFeesIncluded": 0, + "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000015" } ``` - The `amount` is specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. + Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. + + + + To review the fees before any funds move, omit `immediatelyExecute` (it defaults to `false`), inspect the returned quote, and then call `POST /quotes/{quoteId}/execute` before `expiresAt`. Same-currency transfers have no exchange rate to review, so immediate execution is usually what you want. - The transaction is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. Monitor the status by: + The quote's `transactionId` identifies the transaction that carries the payment. It is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. Monitor the status by: You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses. The webhook body contains the full transaction resource: diff --git a/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx b/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx index 2170b9d21..621eda018 100644 --- a/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx +++ b/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx @@ -53,7 +53,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-account This endpoint returns the updated `InternalAccount` object with the new balance. -Alternatively, you can also fund internal accounts using the `/quotes` or `/transfer-in` endpoints as described below. +Alternatively, you can also fund internal accounts using the `/quotes` endpoint as described below. ## Testing Transfer Scenarios @@ -67,19 +67,23 @@ Alternatively, you can also fund internal accounts using the `/quotes` or `/tran ### Testing Transfer-In (Pull from External Account) -When you call `/transfer-in` with an external account created using test patterns, the transfer will complete instantly in sandbox with the behavior determined by the account number: +When you create a quote whose source is an external account created using test patterns, the transfer will complete instantly in sandbox with the behavior determined by the account number: ```bash -POST /transfer-in +POST /quotes { "source": { + "sourceType": "ACCOUNT", "accountId": "ExternalAccount:abc123" // Uses test pattern from creation }, "destination": { + "destinationType": "ACCOUNT", "accountId": "InternalAccount:xyz789" }, - "amount": 10000 // $100 in cents + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 10000, // $100 in cents + "immediatelyExecute": true } ``` @@ -90,16 +94,20 @@ POST /transfer-in Transfer-out works the same way - the destination external account's test pattern determines the outcome: ```bash -POST /transfer-out +POST /quotes { "source": { + "sourceType": "ACCOUNT", "accountId": "InternalAccount:xyz789" }, "destination": { + "destinationType": "ACCOUNT", "accountId": "ExternalAccount:abc123" // Uses test pattern }, - "amount": 10000 + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 10000, + "immediatelyExecute": true } ``` @@ -229,22 +237,22 @@ Test each failure mode systematically: # Create external account ending in 002 POST /customers/external-accounts { "accountNumber": "000000002" } -# Attempt transfer-in - should fail immediately -POST /transfer-in +# Attempt to pull from it - should fail immediately +POST /quotes # 2. Test account closed # Create external account ending in 003 POST /customers/external-accounts { "accountNumber": "000000003" } -# Attempt transfer-out - should fail immediately -POST /transfer-out +# Attempt to push to it - should fail immediately +POST /quotes # 3. Test timeout scenario # Create external account ending in 005 POST /customers/external-accounts { "accountNumber": "000000005" } # Attempt transfer - should pend then fail after ~30s -POST /transfer-in +POST /quotes # Check status immediately - will show PENDING GET /transactions/{transactionId} # Wait 30s, check again - will show FAILED diff --git a/mintlify/platform-overview/core-concepts/account-model.mdx b/mintlify/platform-overview/core-concepts/account-model.mdx index e1089349e..c6559d1e1 100644 --- a/mintlify/platform-overview/core-concepts/account-model.mdx +++ b/mintlify/platform-overview/core-concepts/account-model.mdx @@ -218,19 +218,9 @@ You'll receive `INTERNAL_ACCOUNT.BALANCE_UPDATED` webhooks as balance changes, a - Send funds from internal account to external account: - - ```bash - POST /transfer-out - - { - "source": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "destination": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "amount": 100000 - } - ``` - - Or via quote for cross-currency: + Send funds from internal account to external account. Use `/quotes` for both + same-currency and cross-currency payouts; `immediatelyExecute` moves the funds + in the same request: ```bash POST /quotes @@ -239,21 +229,27 @@ You'll receive `INTERNAL_ACCOUNT.BALANCE_UPDATED` webhooks as balance changes, a "source": {"sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, "destination": {"destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 100000 + "lockedCurrencyAmount": 100000, + "immediatelyExecute": true } ``` + + Omit `immediatelyExecute` to review the rate and fees first, then call + `POST /quotes/{quoteId}/execute`. Pull funds from external account to internal account: ```bash - POST /transfer-in + POST /quotes { - "source": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "destination": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "amount": 100000 + "source": {"sourceType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, + "destination": {"destinationType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 100000, + "immediatelyExecute": true } ``` diff --git a/mintlify/platform-overview/core-concepts/quote-system.mdx b/mintlify/platform-overview/core-concepts/quote-system.mdx index 766761168..e7ef7cd82 100644 --- a/mintlify/platform-overview/core-concepts/quote-system.mdx +++ b/mintlify/platform-overview/core-concepts/quote-system.mdx @@ -5,7 +5,7 @@ description: "How exchange rates, pricing, and payment execution work" "og:image": "/images/og/og-get-started.png" --- -Quotes are Grid's mechanism for providing locked-in exchange rates, transparent fee calculations, and payment instructions. Understanding quotes is essential for all cross-currency or crypto-involved transactions. +Quotes are Grid's mechanism for providing locked-in exchange rates, transparent fee calculations, and payment instructions. Every transfer goes through a quote, whether or not a currency conversion is involved. ## What is a Quote? @@ -20,26 +20,25 @@ Quotes ensure that your customers know exactly what they'll pay and what the rec ## When Do You Need a Quote? - - - Use quotes for: - - **Cross-currency transfers** (USD → EUR, BRL → MXN) - - **Fiat-to-crypto conversion** (USD → BTC) - - **Crypto-to-fiat conversion** (BTC → USD) - - **UMA payments** (always require quotes) - - **JIT funded payments** (need payment instructions) - - These scenarios involve currency conversion, exchange rate risk, or complex routing. - +Use `POST /quotes` for every transfer: - - For same-currency transfers, use simpler endpoints: - - `POST /transfer-out` - Send from internal to external account (same currency) - - `POST /transfer-in` - Pull from external to internal account (same currency) +- **Cross-currency transfers** (USD → EUR, BRL → MXN) +- **Fiat-to-crypto conversion** (USD → BTC) +- **Crypto-to-fiat conversion** (BTC → USD) +- **UMA payments** +- **JIT funded payments** (need payment instructions) +- **Same-currency transfers** (USD → USD), including pulling funds from a + pullable external account into an internal account - No quote needed because there's no currency conversion. - - +For a same-currency transfer there is no exchange rate to lock, so the two-step +create-then-execute flow rarely buys you anything. Set `immediatelyExecute` to +`true` and the quote is created and executed in a single request. + + + The `/transfer-out` and `/transfer-in` endpoints previously served same-currency + transfers and are now deprecated. They still work and their request and response + shapes are unchanged, but new integrations should use `/quotes`. + ## Creating a Quote diff --git a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx index 4722f551d..a31bb254c 100644 --- a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx +++ b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx @@ -74,17 +74,27 @@ Most transactions on Grid are completed in seconds. ## Same-Currency Transfers -For same-currency transfers without quotes: +Same-currency transfers use `/quotes` like every other transfer. With +`immediatelyExecute` set to `true`, the quote is created and executed in one +request. -### Transfer-Out (Internal → External) + + The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work + and their request and response shapes are unchanged, but new integrations should + use `/quotes`. + + +### Internal → External ```bash -POST /transfer-out +POST /quotes { - "source": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "destination": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "amount": 100000 + "source": {"sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, + "destination": {"destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 100000, + "immediatelyExecute": true } ``` @@ -92,24 +102,26 @@ POST /transfer-out ```json { - "id": "Transaction:...", - "status": "PENDING", - "type": "OUTGOING", - "direction": "DEBIT" + "id": "Quote:...", + "status": "PROCESSING", + "transactionId": "Transaction:..." } ``` -Follows same lifecycle as quote-based outgoing transactions. +The transaction referenced by `transactionId` follows the same lifecycle as any +other outgoing transaction. -### Transfer-In (External → Internal) +### External → Internal ```bash -POST /transfer-in +POST /quotes { - "source": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "destination": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "amount": 100000 + "source": {"sourceType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, + "destination": {"destinationType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 100000, + "immediatelyExecute": true } ``` @@ -196,7 +208,7 @@ Where a refund lands depends on how the transaction was funded: - **Real-time (JIT) funding via crypto** (for example USDC or BTC) — the refunded amount is credited to the customer's **internal account balance for that asset**. It is **not** returned to the wallet the funds were sent from. - Crypto deposits are not refunded to the source wallet because Grid cannot verify that the sending wallet belongs to your customer — the funds may have originated from an exchange or another third-party address. The credited internal balance is reusable: spend it with a new quote, or withdraw it to an external wallet with `POST /transfer-out`. + Crypto deposits are not refunded to the source wallet because Grid cannot verify that the sending wallet belongs to your customer — the funds may have originated from an exchange or another third-party address. The credited internal balance is reusable: spend it with a new quote, or withdraw it to an external wallet with a quote whose destination is that wallet. ## Webhooks diff --git a/mintlify/ramps/conversion-flows/self-custody-wallets.mdx b/mintlify/ramps/conversion-flows/self-custody-wallets.mdx index 519d5e015..6018a7609 100644 --- a/mintlify/ramps/conversion-flows/self-custody-wallets.mdx +++ b/mintlify/ramps/conversion-flows/self-custody-wallets.mdx @@ -173,24 +173,25 @@ console.log("Will receive:", `${quote.totalReceivingAmount / 100000000} BTC`); Transfer crypto from internal account to user's wallet: ```javascript -// Same-currency transfer (no quote needed) -const transaction = await fetch( - "https://api.lightspark.com/grid/2025-10-13/transfer-out", - { - method: "POST", - body: JSON.stringify({ - source: { - accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", - }, - destination: { - accountId: externalAccount.id, - }, - amount: 100000, // 0.001 BTC in satoshis - }), - } -).then((r) => r.json()); +// Same-currency transfer: immediatelyExecute creates and executes in one request +const quote = await fetch("https://api.lightspark.com/grid/2025-10-13/quotes", { + method: "POST", + body: JSON.stringify({ + source: { + sourceType: "ACCOUNT", + accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", + }, + destination: { + destinationType: "ACCOUNT", + accountId: externalAccount.id, + }, + lockedCurrencySide: "SENDING", + lockedCurrencyAmount: 100000, // 0.001 BTC in satoshis + immediatelyExecute: true, + }), +}).then((r) => r.json()); -console.log("Transfer initiated:", transaction.id); +console.log("Transfer initiated:", quote.transactionId); ``` ### Step 4: Monitor transfer completion diff --git a/mintlify/ramps/onboarding/implementation-overview.mdx b/mintlify/ramps/onboarding/implementation-overview.mdx index e8f292b3c..0f3c22054 100644 --- a/mintlify/ramps/onboarding/implementation-overview.mdx +++ b/mintlify/ramps/onboarding/implementation-overview.mdx @@ -132,7 +132,7 @@ Implement operational processes to keep your ledger in sync. Use Sandbox to build and validate end‑to‑end without moving real funds. - Simulate fiat funding using `/sandbox/send` endpoint -- Simulate crypto deposits using `/sandbox/internal-accounts/{accountId}/fund` endpoint or `/transfer-in` endpoint +- Simulate crypto deposits using `/sandbox/internal-accounts/{accountId}/fund` endpoint or a `/quotes` call that pulls from a pullable external account - Test quote creation, conversion, and webhook lifecycles - Validate customer onboarding flows with test data diff --git a/mintlify/rewards/platform-tools/sandbox-testing.mdx b/mintlify/rewards/platform-tools/sandbox-testing.mdx index 317fe5d55..6d208dabb 100644 --- a/mintlify/rewards/platform-tools/sandbox-testing.mdx +++ b/mintlify/rewards/platform-tools/sandbox-testing.mdx @@ -154,7 +154,7 @@ Note that these failure test patterns work for any external account type. If you you can create an external account with the appropriate test pattern and use that for the quote source for funding. There are also two other failure test patterns relevant for bank accounts: -- **002**: Insufficient funds (transfer-in will fail) +- **002**: Insufficient funds (pulling from the account will fail) - **004**: Transfer rejected (bank rejects the transfer) ## Testing Customer Onboarding diff --git a/mintlify/snippets/error-handling.mdx b/mintlify/snippets/error-handling.mdx index 9e7173bd1..a7e4d4914 100644 --- a/mintlify/snippets/error-handling.mdx +++ b/mintlify/snippets/error-handling.mdx @@ -98,7 +98,7 @@ async function safeSendPayment(accountId, amount) { ); } - return await createTransferOut({ accountId, amount }); + return await createQuote({ accountId, amount }); } ``` @@ -564,7 +564,7 @@ async function createTransferIdempotent(params) { } try { - const result = await createTransferOut(params); + const result = await createQuote(params); processedTransactions.add(idempotencyKey); return result; } catch (error) { diff --git a/mintlify/snippets/sending/same-currency.mdx b/mintlify/snippets/sending/same-currency.mdx index 879698b9c..5711866c5 100644 --- a/mintlify/snippets/sending/same-currency.mdx +++ b/mintlify/snippets/sending/same-currency.mdx @@ -1,6 +1,10 @@ ## Same-Currency Transfers -Use the `/transfer-out` endpoint when sending funds in the same currency (no exchange rate needed). This is the simplest and fastest option for domestic transfers. +Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. + + + The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work and their request and response shapes are unchanged, but new integrations should use `/quotes`. + ### When to use same-currency transfers @@ -23,17 +27,19 @@ Note the `id` fields from both the internal and external accounts you want to us - - Create the transfer by specifying the source and destination accounts: + + Create the quote by specifying the source and destination accounts. Set `immediatelyExecute` to `true` to move the funds in the same request: ```bash -curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \ +curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -H 'Content-Type: application/json' \ -d '{ - "source": { "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, - "destination": { "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", "paymentRail": "ACH" }, - "amount": 12550, + "source": { "sourceType": "ACCOUNT", "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, + "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", "paymentRail": "ACH" }, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 12550, + "immediatelyExecute": true, "remittanceInformation": "INV-12345" }' ``` @@ -46,12 +52,16 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \ `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail. + + `lockedCurrencySide` tells Grid which side of the transfer the amount applies to. For a same-currency transfer the two sides are equal, so `SENDING` is the natural choice. + + ```json Success (201 Created) { - "id": "Transaction:019542f5-b3e7-1d02-0000-000000000015", - "status": "PENDING", - "type": "OUTGOING", - "direction": "DEBIT", + "id": "Quote:019542f5-b3e7-1d02-0000-000000000006", + "status": "PROCESSING", + "createdAt": "2025-10-03T15:00:00Z", + "expiresAt": "2025-10-03T15:05:00Z", "source": { "sourceType": "ACCOUNT", "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" @@ -60,22 +70,28 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \ "destinationType": "ACCOUNT", "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" }, - "sentAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "receivedAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "platformCustomerId": "customer_12345", - "createdAt": "2025-10-03T15:00:00Z", - "settledAt": null + "sendingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 }, + "receivingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 }, + "totalSendingAmount": 12550, + "totalReceivingAmount": 12550, + "exchangeRate": 1, + "feesIncluded": 0, + "platformFeesIncluded": 0, + "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000015" } ``` - The `amount` is specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. + Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. + + + + To review the fees before any funds move, omit `immediatelyExecute` (it defaults to `false`), inspect the returned quote, and then call `POST /quotes/{quoteId}/execute` before `expiresAt`. Same-currency transfers have no exchange rate to review, so immediate execution is usually what you want. - The transaction is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses. The webhook body contains the full transaction resource: + The quote's `transactionId` identifies the transaction that carries the payment. It is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses. The webhook body contains the full transaction resource: ```json { @@ -100,4 +116,3 @@ If a transaction fails, Grid initiates a refund automatically. You'll receive `O - diff --git a/mintlify/snippets/terminology.mdx b/mintlify/snippets/terminology.mdx index 68f38d4db..83b24b4e6 100644 --- a/mintlify/snippets/terminology.mdx +++ b/mintlify/snippets/terminology.mdx @@ -133,9 +133,8 @@ Understanding how entities map to your specific use case helps clarify your inte - Includes amounts, currencies, and settlement information - May include counterparty information for compliance purposes if required by your platform configuration -Transactions are created when: -- A quote is executed (either incoming or outgoing) -- A same currency transfer is initiated (transfer-in or transfer-out) +Transactions are created when a quote is executed, incoming or outgoing. This covers +same-currency and cross-currency transfers alike. ### UMA Addresses (optional) diff --git a/openapi.yaml b/openapi.yaml index 51644742f..d20405c37 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -36,9 +36,9 @@ tags: - name: VASPs description: Directory of Virtual Asset Service Providers (exchanges and other custodial platforms) recognized for counterparty declarations. - name: Same-Currency Transfers - description: Endpoints for transferring funds between internal and external accounts with the same currency + description: Deprecated endpoints for transferring funds between internal and external accounts with the same currency. Use the quote endpoints under Cross-Currency Transfers instead, which now serve same-currency transfers as well. - name: Cross-Currency Transfers - description: Endpoints for creating and confirming quotes for cross-currency transfers + description: Endpoints for creating and confirming quotes for transfers, both same-currency and cross-currency - name: Transactions description: Endpoints for retrieving transaction information - name: Webhooks @@ -3593,8 +3593,29 @@ paths: /transfer-in: post: summary: Create a transfer-in request + deprecated: true description: | - Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an external account source and an internal account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + + Transfer funds from an external account to an internal account for a specific customer. + This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). + Otherwise, use the paymentInstructions on the internal account to deposit funds. operationId: createTransferIn tags: - Same-Currency Transfers @@ -3658,7 +3679,27 @@ paths: /transfer-out: post: summary: Create a transfer-out request + deprecated: true description: | + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an internal account source and an external account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs and `destination.paymentRail` are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - `remittanceInformation` and `purposeOfPayment` carry over unchanged + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + Transfer funds from an internal account to an external account for a specific customer. operationId: createTransferOut tags: @@ -3924,12 +3965,12 @@ paths: post: summary: Create a transfer quote description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. + Generate a quote for a transfer between any combination of accounts + and UMA addresses. This endpoint handles same-currency and cross-currency + transfers alike, and provides the necessary instructions to execute the transfer. **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to Account**: Transfer between internal/external accounts, with or without currency exchange. - **Account to UMA**: Transfer from an internal account to an UMA address. - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. @@ -3938,8 +3979,9 @@ paths: - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. + **Same-currency transfers:** Use this endpoint for same-currency transfers too. + Set `immediatelyExecute: true` to create and execute in a single request. The + older `/transfer-in` and `/transfer-out` endpoints are deprecated. Requires a token with the `TRANSACT` permission; `VIEW` alone is not sufficient. A quote is the instrument a later execute draws on, and @@ -3965,6 +4007,21 @@ paths: schema: $ref: '#/components/schemas/QuoteRequest' examples: + sameCurrencyAccountToAccount: + summary: Same-Currency Account to Account Transfer (immediate execution) + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + lockedCurrencySide: SENDING + lockedCurrencyAmount: 12550 + immediatelyExecute: true + remittanceInformation: INV-12345 + description: Same-currency payout, no exchange required. accountToAccount: summary: Account to Account Transfer value: diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 8c164aafd..aba89ec4f 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -45,9 +45,15 @@ tags: Directory of Virtual Asset Service Providers (exchanges and other custodial platforms) recognized for counterparty declarations. - name: Same-Currency Transfers - description: Endpoints for transferring funds between internal and external accounts with the same currency + description: >- + Deprecated endpoints for transferring funds between internal and external + accounts with the same currency. Use the quote endpoints under + Cross-Currency Transfers instead, which now serve same-currency transfers + as well. - name: Cross-Currency Transfers - description: Endpoints for creating and confirming quotes for cross-currency transfers + description: >- + Endpoints for creating and confirming quotes for transfers, both + same-currency and cross-currency - name: Transactions description: Endpoints for retrieving transaction information - name: Webhooks diff --git a/openapi/paths/quotes/quotes.yaml b/openapi/paths/quotes/quotes.yaml index 973b4ada7..cde5a52be 100644 --- a/openapi/paths/quotes/quotes.yaml +++ b/openapi/paths/quotes/quotes.yaml @@ -1,12 +1,12 @@ post: summary: Create a transfer quote description: | - Generate a quote for a cross-currency transfer between any combination of accounts - and UMA addresses. This endpoint handles currency exchange and provides the necessary - instructions to execute the transfer. + Generate a quote for a transfer between any combination of accounts + and UMA addresses. This endpoint handles same-currency and cross-currency + transfers alike, and provides the necessary instructions to execute the transfer. **Transfer Types Supported:** - - **Account to Account**: Transfer between internal/external accounts with currency exchange. + - **Account to Account**: Transfer between internal/external accounts, with or without currency exchange. - **Account to UMA**: Transfer from an internal account to an UMA address. - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account. @@ -15,8 +15,9 @@ post: - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution - **Important:** If you are transferring funds in the same currency (no exchange required), - use the `/transfer-in` or `/transfer-out` endpoints instead. + **Same-currency transfers:** Use this endpoint for same-currency transfers too. + Set `immediatelyExecute: true` to create and execute in a single request. The + older `/transfer-in` and `/transfer-out` endpoints are deprecated. Requires a token with the `TRANSACT` permission; `VIEW` alone is not sufficient. A quote is the instrument a later execute draws on, and @@ -43,6 +44,21 @@ post: schema: $ref: ../../components/schemas/quotes/QuoteRequest.yaml examples: + sameCurrencyAccountToAccount: + summary: Same-Currency Account to Account Transfer (immediate execution) + value: + source: + sourceType: ACCOUNT + accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 + destination: + destinationType: ACCOUNT + accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + paymentRail: ACH + lockedCurrencySide: SENDING + lockedCurrencyAmount: 12550 + immediatelyExecute: true + remittanceInformation: INV-12345 + description: 'Same-currency payout, no exchange required.' accountToAccount: summary: Account to Account Transfer value: diff --git a/openapi/paths/transfers/transfer_in.yaml b/openapi/paths/transfers/transfer_in.yaml index 9dff344f9..d78c474e2 100644 --- a/openapi/paths/transfers/transfer_in.yaml +++ b/openapi/paths/transfers/transfer_in.yaml @@ -1,6 +1,25 @@ post: summary: Create a transfer-in request - description: > + deprecated: true + description: | + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an external account source and an internal account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + Transfer funds from an external account to an internal account for a specific customer. This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the paymentInstructions on the internal account to deposit funds. diff --git a/openapi/paths/transfers/transfer_out.yaml b/openapi/paths/transfers/transfer_out.yaml index a76e9e80f..d1c773af7 100644 --- a/openapi/paths/transfers/transfer_out.yaml +++ b/openapi/paths/transfers/transfer_out.yaml @@ -1,6 +1,26 @@ post: summary: Create a transfer-out request - description: > + deprecated: true + description: | + **Deprecated. Use `POST /quotes` instead.** + + Same-currency transfers are now served by the quote endpoint. Create a quote + with an internal account source and an external account destination and set + `immediatelyExecute: true` to move the funds in a single request, exactly as + this endpoint does. This endpoint continues to work and its request and + response shapes are unchanged. + + To migrate a request to `POST /quotes`: + + - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to + `destination`; the account IDs and `destination.paymentRail` are unchanged + - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING` + - `remittanceInformation` and `purposeOfPayment` carry over unchanged + - add `immediatelyExecute: true` to keep the single-request behavior + + The quote response is a `Quote` rather than a `Transaction`; read + `transactionId` from it to track the resulting transaction. + Transfer funds from an internal account to an external account for a specific customer. operationId: createTransferOut tags: From 63bd3fb8127e606542fbb673badbe188afe00371 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 00:03:33 +0000 Subject: [PATCH 2/9] docs: cut repeated deprecation notices down to one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first pass repeated the same "use /quotes instead" note on four pages, so a reader working through core concepts and then a payment guide hit it three times. Keep one canonical note on the quote system page, which is where the "when do I need a quote?" question is actually answered. Drop it from the two task guides — someone following those just needs the correct call — and from the transaction lifecycle page. Also collapse the lifecycle page's Same-Currency Transfers section. It existed because same-currency used to be a genuinely different API path; now that it is an ordinary quote, the two request blocks just re-showed the lifecycle walked through immediately above them. Replaced with a short paragraph and a link to the worked example. The API reference deprecation badges and the changelog entry carry the announcement for anyone arriving from the old endpoints. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .../payment-flow/send-payment.mdx | 4 -- .../core-concepts/transaction-lifecycle.mdx | 60 +++---------------- mintlify/snippets/sending/same-currency.mdx | 4 -- 3 files changed, 8 insertions(+), 60 deletions(-) diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index c3e7948f5..1f40be2f4 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -41,10 +41,6 @@ Before sending payments, ensure you have: Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. - - The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work and their request and response shapes are unchanged, but new integrations should use `/quotes`. - - ### When to use same-currency transfers - Transferring USD from a USD internal account to a USD external account diff --git a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx index a31bb254c..d5c12843c 100644 --- a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx +++ b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx @@ -74,58 +74,14 @@ Most transactions on Grid are completed in seconds. ## Same-Currency Transfers -Same-currency transfers use `/quotes` like every other transfer. With -`immediatelyExecute` set to `true`, the quote is created and executed in one -request. - - - The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work - and their request and response shapes are unchanged, but new integrations should - use `/quotes`. - - -### Internal → External - -```bash -POST /quotes - -{ - "source": {"sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "destination": {"destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 100000, - "immediatelyExecute": true -} -``` - -**Response:** - -```json -{ - "id": "Quote:...", - "status": "PROCESSING", - "transactionId": "Transaction:..." -} -``` - -The transaction referenced by `transactionId` follows the same lifecycle as any -other outgoing transaction. - -### External → Internal - -```bash -POST /quotes - -{ - "source": {"sourceType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"}, - "destination": {"destinationType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"}, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 100000, - "immediatelyExecute": true -} -``` - -Only works for "pullable" external accounts (e.g., debit cards). +Same-currency transfers go through `/quotes` and follow the lifecycle above +unchanged. Setting `immediatelyExecute` to `true` collapses the create and execute +steps into one request; the quote's `transactionId` identifies the resulting +transaction. Pulling from an external account into an internal one works the same +way, and requires a "pullable" external account (e.g., debit cards). + +See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#same-currency-transfers) +for a worked example. ## Outgoing Payment Status diff --git a/mintlify/snippets/sending/same-currency.mdx b/mintlify/snippets/sending/same-currency.mdx index 5711866c5..dc8344076 100644 --- a/mintlify/snippets/sending/same-currency.mdx +++ b/mintlify/snippets/sending/same-currency.mdx @@ -2,10 +2,6 @@ Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. - - The `/transfer-out` and `/transfer-in` endpoints are deprecated. They still work and their request and response shapes are unchanged, but new integrations should use `/quotes`. - - ### When to use same-currency transfers - Transferring USD from a USD internal account to a USD external account From 66ec72bc4dd429d2683ce0e68f0ab22ad828d623 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 00:16:14 +0000 Subject: [PATCH 3/9] docs: merge the send-payment flow and drop the agent transfer APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two structural cleanups now that quotes are the single path. send-payment.mdx had two parallel ~140-line walkthroughs, one for same-currency and one for cross-currency, that both called POST /quotes. The split dated from when same-currency was a genuinely different API. Merged them into one "Send a payment" flow whose real fork is one-step (`immediatelyExecute`) versus two-step (review the rate, then execute) — which is the choice that actually exists, and is not the same question as whether the currencies differ. That also resolved two "Transaction statuses" tables which described the same statuses differently; they are now one reconciled table. The two-card Overview grid framing "two payment methods" is gone. 511 lines down to 369. Removed /agents/me/transfer-in and /agents/me/transfer-out outright rather than deprecating them: there is no handler for either in sparkcore, so nothing can be using them, and agent transfers will go through quotes when they are built. Removing the endpoints orphaned the rest of the transfer-shaped agent model, so that goes too: the TRANSFER_OUT and TRANSFER_IN action types (nothing could produce them), AgentTransferDetails and AgentAction.transferDetails, and the CREATE_TRANSFERS permission, which gated only these two endpoints — CREATE_QUOTES and EXECUTE_QUOTES cover the quote-based path. Stainless config and the agent guides follow. oasdiff reports 6 errors and 7 warnings for the removals, so this PR picks up the breaking-change label and needs API-reviewer sign-off. The workflow does not fail on findings. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .../skills/grid-api/references/endpoints.md | 2 - .stainless/stainless.yml | 3 - mintlify/changelog.mdx | 2 +- .../agents/approvals-and-audit.mdx | 5 +- .../agents/policies-and-permissions.mdx | 5 +- mintlify/openapi.yaml | 186 +----------- .../payment-flow/send-payment.mdx | 281 +++++------------- .../core-concepts/transaction-lifecycle.mdx | 2 +- openapi.yaml | 186 +----------- .../schemas/agents/AgentAction.yaml | 11 +- .../schemas/agents/AgentActionType.yaml | 6 +- .../schemas/agents/AgentPermission.yaml | 6 +- .../schemas/agents/AgentTransferDetails.yaml | 25 -- openapi/openapi.yaml | 4 - .../paths/agents/agents_me_transfer-in.yaml | 85 ------ .../paths/agents/agents_me_transfer-out.yaml | 81 ----- 16 files changed, 86 insertions(+), 804 deletions(-) delete mode 100644 openapi/components/schemas/agents/AgentTransferDetails.yaml delete mode 100644 openapi/paths/agents/agents_me_transfer-in.yaml delete mode 100644 openapi/paths/agents/agents_me_transfer-out.yaml diff --git a/.claude/skills/grid-api/references/endpoints.md b/.claude/skills/grid-api/references/endpoints.md index 3089212e9..977891c24 100644 --- a/.claude/skills/grid-api/references/endpoints.md +++ b/.claude/skills/grid-api/references/endpoints.md @@ -283,8 +283,6 @@ The `/agents/me/*` endpoints are called by an agent using its own credentials (A | POST | `/agents/me/quotes/{quoteId}/execute` | Execute a quote | | GET | `/agents/me/actions` | List agent's own actions | | GET | `/agents/me/actions/{actionId}` | Get an agent action | -| POST | `/agents/me/transfer-in` | Create a transfer-in | -| POST | `/agents/me/transfer-out` | Create a transfer-out | | GET | `/agents/me/internal-accounts` | List agent's internal accounts | | GET | `/agents/me/external-accounts` | List agent external accounts | | POST | `/agents/me/external-accounts` | Add an external account | diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index a4e97538e..f9236c1d2 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -505,7 +505,6 @@ resources: pkr_beneficiary: "#/components/schemas/PkrBeneficiary" ethereum_wallet_external_account_info: "#/components/schemas/EthereumWalletExternalAccountInfo" verification_error: "#/components/schemas/VerificationError" - agent_transfer_details: "#/components/schemas/AgentTransferDetails" slv_external_account_create_info: "#/components/schemas/SlvExternalAccountCreateInfo" slv_beneficiary: "#/components/schemas/SlvBeneficiary" individual_customer: "#/components/schemas/IndividualCustomer" @@ -624,8 +623,6 @@ resources: me: methods: retrieve: get /agents/me - create_transfer_in: post /agents/me/transfer-in - create_transfer_out: post /agents/me/transfer-out list_internal_accounts: get /agents/me/internal-accounts subresources: transactions: diff --git a/mintlify/changelog.mdx b/mintlify/changelog.mdx index c5b5f0661..e5678f5b9 100644 --- a/mintlify/changelog.mdx +++ b/mintlify/changelog.mdx @@ -27,7 +27,7 @@ same-currency and cross-currency alike. - `POST /transfer-in` and `POST /transfer-out` continue to work with unchanged request and response shapes. -See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#same-currency-transfers) +See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#send-a-payment) for the updated flow. ## Assess your own fees on every transaction diff --git a/mintlify/global-accounts/agents/approvals-and-audit.mdx b/mintlify/global-accounts/agents/approvals-and-audit.mdx index 34a5ce1d0..c178d608c 100644 --- a/mintlify/global-accounts/agents/approvals-and-audit.mdx +++ b/mintlify/global-accounts/agents/approvals-and-audit.mdx @@ -50,9 +50,8 @@ An approval is not always a guarantee that the action will execute. Your product - `agentId` — the agent that submitted the action - `customerId` / `platformCustomerId` — the customer on whose behalf the agent acted - `status` — `PENDING_APPROVAL` while awaiting a decision; transitions to `APPROVED`, `REJECTED`, or `FAILED` -- `type` — `EXECUTE_QUOTE`, `TRANSFER_OUT`, or `TRANSFER_IN` -- `quote` — for `EXECUTE_QUOTE` actions, the full quote object including amounts, currencies, exchange rate, and destination -- `transferDetails` — for `TRANSFER_OUT` / `TRANSFER_IN` actions, amount, currency, and source/destination account IDs +- `type` — `EXECUTE_QUOTE` +- `quote` — the full quote object including amounts, currencies, exchange rate, and destination - `transaction` — populated after the action is approved and execution begins; absent while pending or rejected - `rejectionReason` — optional reason string set when your platform rejects the action - `createdAt` / `updatedAt` — timestamps for the action lifecycle diff --git a/mintlify/global-accounts/agents/policies-and-permissions.mdx b/mintlify/global-accounts/agents/policies-and-permissions.mdx index 965a96736..f6cd747e6 100644 --- a/mintlify/global-accounts/agents/policies-and-permissions.mdx +++ b/mintlify/global-accounts/agents/policies-and-permissions.mdx @@ -37,9 +37,8 @@ Grid exposes explicit allowlists instead of broad agent access. Available permis | Permission | What it allows | |---|---| | `VIEW_TRANSACTIONS` | List and retrieve transactions and account balances | -| `CREATE_TRANSFERS` | Initiate same-currency transfers | -| `CREATE_QUOTES` | Create cross-currency quotes | -| `EXECUTE_QUOTES` | Execute cross-currency quotes | +| `CREATE_QUOTES` | Create quotes | +| `EXECUTE_QUOTES` | Execute quotes | | `MANAGE_EXTERNAL_ACCOUNTS` | Create and manage external accounts | These permissions are intentionally narrow and map to concrete Grid-backed actions rather than broad scopes such as "manage wallet." diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index d20405c37..21f570d74 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8111,151 +8111,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: - post: - summary: Create a transfer-in - description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: - post: - summary: Create a transfer-out - description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' /agents/me/internal-accounts: get: summary: List agent's internal accounts @@ -23936,11 +23791,10 @@ components: type: string enum: - VIEW_TRANSACTIONS - - CREATE_TRANSFERS - CREATE_QUOTES - EXECUTE_QUOTES - MANAGE_EXTERNAL_ACCOUNTS - description: 'Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. CREATE_TRANSFERS: Can initiate same-currency transfers. CREATE_QUOTES: Can create cross-currency quotes. EXECUTE_QUOTES: Can execute cross-currency quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts.' + description: 'Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. CREATE_QUOTES: Can create quotes. EXECUTE_QUOTES: Can execute quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts.' AgentExecutionMode: type: string enum: @@ -24218,42 +24072,12 @@ components: type: string enum: - EXECUTE_QUOTE - - TRANSFER_OUT - - TRANSFER_IN description: | The type of action the agent is requesting. | Type | Description | |------|-------------| - | `EXECUTE_QUOTE` | Execute a cross-currency quote | - | `TRANSFER_OUT` | Transfer from an internal account to an external account | - | `TRANSFER_IN` | Transfer from an external account to an internal account | - AgentTransferDetails: - type: object - description: Details of a transfer-type agent action (TRANSFER_OUT or TRANSFER_IN). - required: - - amount - - currency - - sourceAccountId - - destinationAccountId - properties: - amount: - type: integer - format: int64 - description: Transfer amount in the smallest unit of the specified currency. - example: 50000 - currency: - type: string - description: ISO 4217 currency code for the transfer amount. - example: USD - sourceAccountId: - type: string - description: ID of the source account (internal or external). - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destinationAccountId: - type: string - description: ID of the destination account (internal or external). - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + | `EXECUTE_QUOTE` | Execute a quote | AgentAction: type: object description: An action submitted by an agent that may require platform approval before execution. All agent-initiated operations (quote execution, transfers) are represented as AgentActions, giving the platform a consistent object to approve, reject, and audit regardless of the underlying operation type. @@ -24290,11 +24114,7 @@ components: quote: allOf: - $ref: '#/components/schemas/Quote' - description: The quote being executed. Populated for `EXECUTE_QUOTE` actions; absent for transfer actions. Contains the full amount, currency, destination, and rate details needed to present an approval decision to the user. - transferDetails: - allOf: - - $ref: '#/components/schemas/AgentTransferDetails' - description: Details of the transfer being requested. Populated for `TRANSFER_OUT` and `TRANSFER_IN` actions; absent for `EXECUTE_QUOTE` actions. + description: The quote being executed. Contains the full amount, currency, destination, and rate details needed to present an approval decision to the user. transaction: allOf: - $ref: '#/components/schemas/TransactionOneOf' diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index 1f40be2f4..aeda8706e 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -1,26 +1,30 @@ --- title: "Sending Payments" -description: "Learn how to send payments from internal accounts to external bank accounts with same-currency and cross-currency transfers" +description: "Learn how to send payments from internal accounts to external bank accounts, with or without currency conversion" icon: "/images/icons/paper-plane-top-right.svg" "og:image": "/images/og/og-payouts-b2b.png" --- import { FeatureCard, FeatureCardGrid } from '/snippets/feature-card.mdx'; -Send payments from your customers' internal accounts to their external bank accounts or to other destinations. Grid supports both same-currency transfers and cross-currency transfers with automatic exchange rate handling. +Send payments from your customers' internal accounts to their external bank accounts or to other destinations, with automatic exchange rate handling when the currencies differ. ## Overview -Grid provides two payment methods depending on your use case: +Every payment goes through `POST /quotes`, whether or not the currencies differ. A quote +prices the transfer — the amounts, the fees, and, when the currencies differ, the exchange +rate — and creates the transaction that carries the money. - - - Send funds in the same currency from an internal account to an external account. Fast and straightforward. - - - Send funds with currency conversion using real-time exchange rates. Supports multiple fiat currencies and payment rails. - - +What varies is when you execute it: + +- **In one request.** Set `immediatelyExecute` and Grid creates and executes the quote + together. This is the usual choice for a same-currency transfer, where there is no rate + to weigh up. +- **In two steps.** Create the quote, check the rate and fees, then call execute before the + quote expires. This is the usual choice when converting currency. + +Either way the request shape is the same, and the payment rail is chosen from the +destination account. ## Prerequisites @@ -37,17 +41,7 @@ Before sending payments, ensure you have: Accounts](/payouts-and-b2b/depositing-funds/external-accounts) guides first. -## Same-Currency Transfers - -Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. - -### When to use same-currency transfers - -- Transferring USD from a USD internal account to a USD external account -- Sending funds within the same country using the same payment rail -- No currency conversion is required - -### Create a transfer +## Send a payment @@ -62,146 +56,8 @@ Note the `id` fields from both the internal and external accounts you want to us - - Create the quote by specifying the source and destination accounts. Set `immediatelyExecute` to `true` to move the funds in the same request: - -```bash cURL -curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ - -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ - -H 'Content-Type: application/json' \ - -d '{ - "source": { - "sourceType": "ACCOUNT", - "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" - }, - "destination": { - "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", - "paymentRail": "ACH" - }, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 12550, - "immediatelyExecute": true, - "remittanceInformation": "INV-12345" - }' -``` - - - The `paymentRail` field is optional. If omitted, Grid selects a default rail for the destination. Specify a rail (e.g., `ACH`, `WIRE`, `RTP`, `FEDNOW`) when you need to control which payment network processes the transfer. - - - - `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail. - - - - `purposeOfPayment` is optional. Some destinations require it, and some rails carry it on - the payment itself. - - - -```json Success (201 Created) -{ - "id": "Quote:019542f5-b3e7-1d02-0000-000000000006", - "status": "PROCESSING", - "createdAt": "2025-10-03T15:00:00Z", - "expiresAt": "2025-10-03T15:05:00Z", - "source": { - "sourceType": "ACCOUNT", - "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" - }, - "destination": { - "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" - }, - "sendingCurrency": { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "decimals": 2 - }, - "receivingCurrency": { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "decimals": 2 - }, - "totalSendingAmount": 12550, - "totalReceivingAmount": 12550, - "exchangeRate": 1, - "feesIncluded": 0, - "platformFeesIncluded": 0, - "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000015" -} -``` - - - Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. - - - - To review the fees before any funds move, omit `immediatelyExecute` (it defaults to `false`), inspect the returned quote, and then call `POST /quotes/{quoteId}/execute` before `expiresAt`. Same-currency transfers have no exchange rate to review, so immediate execution is usually what you want. - - - - - The quote's `transactionId` identifies the transaction that carries the payment. It is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. Monitor the status by: - -You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses. The webhook body contains the full transaction resource: - -```json -{ - "type": "OUTGOING_PAYMENT.COMPLETED", - "data": { - "id": "Transaction:019542f5-b3e7-1d02-0000-000000000015", - "status": "COMPLETED", - "type": "OUTGOING", - "direction": "DEBIT", - "sentAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "receivedAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "settledAt": "2025-10-03T15:02:30Z" - }, - "timestamp": "2025-10-03T15:03:00Z" -} -``` - -If a transaction fails, Grid initiates a refund automatically. You'll receive `OUTGOING_PAYMENT.REFUND_PENDING` followed by `OUTGOING_PAYMENT.REFUND_COMPLETED` or `OUTGOING_PAYMENT.REFUND_FAILED`. The transaction's `refund` object tracks the refund status and reference. - - - For the full state diagram, refund object details, and all webhook scenarios (including bank returns and manual cancellations), see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. - - - - -### Transaction statuses - -| Status | Description | -| ------------ | --------------------------------------------- | -| `PENDING` | Transfer initiated and awaiting processing | -| `EXPIRED` | Quote wasn't executed before the expiry window | -| `PROCESSING` | Transfer in progress through the payment rail | -| `COMPLETED` | Transfer successfully completed | -| `FAILED` | Transfer failed — accompanied by a `failureReason` | - - -For the full state diagram including refund tracking and edge cases like bank returns, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. - - -## Cross-Currency Transfers - -Use the quotes flow when sending funds with currency conversion. This locks in an exchange rate and provides all details needed to execute the transfer. - -### When to use cross-currency transfers - -- Converting USD to EUR, MXN, BRL, or other supported currencies -- Sending international payments with automatic currency conversion -- Need to lock in a specific exchange rate for the transfer - -### Create and execute a quote - - - - Request a quote to lock in the exchange rate and get transfer details: + + Specify the source and destination accounts and the amount to lock: ```bash cURL curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ @@ -214,10 +70,12 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ }, "destination": { "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" + "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", + "paymentRail": "ACH" }, "lockedCurrencySide": "SENDING", "lockedCurrencyAmount": 10000, + "remittanceInformation": "INV-12345", "description": "Payment for services - Invoice #1234" }' ``` @@ -258,69 +116,64 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ } ``` + + **Same-currency transfers use this exact request.** The two currencies simply match, and + the quote comes back with an `exchangeRate` of `1`. Add `"immediatelyExecute": true` to + create and execute in this one request, and skip the next two steps. + + **Locked currency side** determines which amount is fixed: - `SENDING`: Lock the sending amount (receiving amount calculated based on exchange rate) - `RECEIVING`: Lock the receiving amount (sending amount calculated based on exchange rate) + + + The `paymentRail` field is optional. If omitted, Grid selects a default rail for the destination. Specify a rail (e.g., `ACH`, `WIRE`, `RTP`, `FEDNOW`) when you need to control which payment network processes the transfer. + + + + `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail. + + + + `purposeOfPayment` is optional. Some destinations require it, and some rails carry it on + the payment itself. + + + + Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. + - - Before executing, review the quote to ensure: + + Before executing, check that: -- Exchange rate is acceptable +- The exchange rate is acceptable - Fees are as expected -- Receiving amount meets requirements -- Quote hasn't expired (check `expiresAt`) +- The receiving amount meets requirements +- The quote hasn't expired (check `expiresAt`) Quote expiration depends on the corridor but is typically ~5 minutes or greater. If expired, create a new quote to get an updated exchange rate. + + + For a same-currency transfer there is no rate to weigh up, so this step is usually skipped + in favor of `immediatelyExecute`. + Confirm and execute the quote to initiate the transfer: - ```bash cURL curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b3e7-1d02-0000-000000000025/execute' \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" ``` -```json Success (200 OK) -{ - "id": "Quote:019542f5-b3e7-1d02-0000-000000000025", - "status": "PROCESSING", - "createdAt": "2025-10-03T15:00:00Z", - "expiresAt": "2025-10-03T15:15:00Z", - "source": { - "sourceType": "ACCOUNT", - "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" - }, - "destination": { - "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" - }, - "sendingCurrency": { - "code": "USD", - "name": "United States Dollar", - "symbol": "$", - "decimals": 2 - }, - "receivingCurrency": { - "code": "EUR", - "name": "Euro", - "symbol": "€", - "decimals": 2 - }, - "totalSendingAmount": 10000, - "totalReceivingAmount": 9200, - "exchangeRate": 0.92, - "feesIncluded": 50, - "platformFeesIncluded": 0, - "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000030" -} -``` +The quote comes back with `status` `PROCESSING` and the same `transactionId` it carried at +creation. Once executed, the quote creates a transaction and the transfer begins processing. The `transactionId` can be used to track the payment. @@ -331,8 +184,8 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b - - After execution, a transaction is created and progresses through `PENDING` → `PROCESSING` → `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses: + + The quote's `transactionId` identifies the transaction that carries the payment. It progresses through `PENDING` → `PROCESSING` → `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as it does: ```json { @@ -368,13 +221,17 @@ If a transaction fails, Grid initiates a refund automatically. You'll receive `O ### Transaction statuses -| Status | Description | -| ------------ | ------------------------------------------ | -| `PENDING` | Quote created, awaiting execution | -| `PROCESSING` | Quote executed, transfer in progress | -| `COMPLETED` | Transfer successfully completed | -| `FAILED` | Transfer failed — refund initiated automatically (track via `refund` object) | -| `EXPIRED` | Quote expired without execution | +| Status | Description | +| ------------ | --------------------------------------------- | +| `PENDING` | Quote created, awaiting execution | +| `PROCESSING` | Quote executed, transfer in progress through the payment rail | +| `COMPLETED` | Transfer successfully completed | +| `FAILED` | Transfer failed — accompanied by a `failureReason`, and a refund is initiated automatically (track via the `refund` object) | +| `EXPIRED` | Quote wasn't executed before the expiry window | + + +For the full state diagram including refund tracking and edge cases like bank returns, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. + ## Checking Payment Status diff --git a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx index d5c12843c..ee0f5eef0 100644 --- a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx +++ b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx @@ -80,7 +80,7 @@ steps into one request; the quote's `transactionId` identifies the resulting transaction. Pulling from an external account into an internal one works the same way, and requires a "pullable" external account (e.g., debit cards). -See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#same-currency-transfers) +See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#send-a-payment) for a worked example. ## Outgoing Payment Status diff --git a/openapi.yaml b/openapi.yaml index d20405c37..21f570d74 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8111,151 +8111,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /agents/me/transfer-in: - post: - summary: Create a transfer-in - description: | - Transfer funds from an external account to an internal account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to deposit funds. - operationId: agentCreateTransferIn - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInRequest' - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /agents/me/transfer-out: - post: - summary: Create a transfer-out - description: | - Transfer funds from an internal account to an external account for the authenticated agent's customer. Accounts must belong to the agent's customer. Requires the CREATE_TRANSFERS permission in the agent's policy. - If the agent's policy requires approval for this amount, the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: | - A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferOutRequest' - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 - responses: - '201': - description: Action submitted successfully. If the agent's policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. - content: - application/json: - schema: - $ref: '#/components/schemas/AgentAction' - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: '#/components/schemas/Error403' - '404': - description: Account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' /agents/me/internal-accounts: get: summary: List agent's internal accounts @@ -23936,11 +23791,10 @@ components: type: string enum: - VIEW_TRANSACTIONS - - CREATE_TRANSFERS - CREATE_QUOTES - EXECUTE_QUOTES - MANAGE_EXTERNAL_ACCOUNTS - description: 'Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. CREATE_TRANSFERS: Can initiate same-currency transfers. CREATE_QUOTES: Can create cross-currency quotes. EXECUTE_QUOTES: Can execute cross-currency quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts.' + description: 'Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. CREATE_QUOTES: Can create quotes. EXECUTE_QUOTES: Can execute quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts.' AgentExecutionMode: type: string enum: @@ -24218,42 +24072,12 @@ components: type: string enum: - EXECUTE_QUOTE - - TRANSFER_OUT - - TRANSFER_IN description: | The type of action the agent is requesting. | Type | Description | |------|-------------| - | `EXECUTE_QUOTE` | Execute a cross-currency quote | - | `TRANSFER_OUT` | Transfer from an internal account to an external account | - | `TRANSFER_IN` | Transfer from an external account to an internal account | - AgentTransferDetails: - type: object - description: Details of a transfer-type agent action (TRANSFER_OUT or TRANSFER_IN). - required: - - amount - - currency - - sourceAccountId - - destinationAccountId - properties: - amount: - type: integer - format: int64 - description: Transfer amount in the smallest unit of the specified currency. - example: 50000 - currency: - type: string - description: ISO 4217 currency code for the transfer amount. - example: USD - sourceAccountId: - type: string - description: ID of the source account (internal or external). - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destinationAccountId: - type: string - description: ID of the destination account (internal or external). - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + | `EXECUTE_QUOTE` | Execute a quote | AgentAction: type: object description: An action submitted by an agent that may require platform approval before execution. All agent-initiated operations (quote execution, transfers) are represented as AgentActions, giving the platform a consistent object to approve, reject, and audit regardless of the underlying operation type. @@ -24290,11 +24114,7 @@ components: quote: allOf: - $ref: '#/components/schemas/Quote' - description: The quote being executed. Populated for `EXECUTE_QUOTE` actions; absent for transfer actions. Contains the full amount, currency, destination, and rate details needed to present an approval decision to the user. - transferDetails: - allOf: - - $ref: '#/components/schemas/AgentTransferDetails' - description: Details of the transfer being requested. Populated for `TRANSFER_OUT` and `TRANSFER_IN` actions; absent for `EXECUTE_QUOTE` actions. + description: The quote being executed. Contains the full amount, currency, destination, and rate details needed to present an approval decision to the user. transaction: allOf: - $ref: '#/components/schemas/TransactionOneOf' diff --git a/openapi/components/schemas/agents/AgentAction.yaml b/openapi/components/schemas/agents/AgentAction.yaml index 105614494..260c11588 100644 --- a/openapi/components/schemas/agents/AgentAction.yaml +++ b/openapi/components/schemas/agents/AgentAction.yaml @@ -38,15 +38,8 @@ properties: allOf: - $ref: ../quotes/Quote.yaml description: >- - The quote being executed. Populated for `EXECUTE_QUOTE` actions; absent for transfer actions. - Contains the full amount, currency, destination, and rate details needed to present an - approval decision to the user. - transferDetails: - allOf: - - $ref: ./AgentTransferDetails.yaml - description: >- - Details of the transfer being requested. Populated for `TRANSFER_OUT` and `TRANSFER_IN` - actions; absent for `EXECUTE_QUOTE` actions. + The quote being executed. Contains the full amount, currency, destination, and rate + details needed to present an approval decision to the user. transaction: allOf: - $ref: ../transactions/TransactionOneOf.yaml diff --git a/openapi/components/schemas/agents/AgentActionType.yaml b/openapi/components/schemas/agents/AgentActionType.yaml index 8b9573470..1b8929b4c 100644 --- a/openapi/components/schemas/agents/AgentActionType.yaml +++ b/openapi/components/schemas/agents/AgentActionType.yaml @@ -1,13 +1,9 @@ type: string enum: - EXECUTE_QUOTE - - TRANSFER_OUT - - TRANSFER_IN description: | The type of action the agent is requesting. | Type | Description | |------|-------------| - | `EXECUTE_QUOTE` | Execute a cross-currency quote | - | `TRANSFER_OUT` | Transfer from an internal account to an external account | - | `TRANSFER_IN` | Transfer from an external account to an internal account | + | `EXECUTE_QUOTE` | Execute a quote | diff --git a/openapi/components/schemas/agents/AgentPermission.yaml b/openapi/components/schemas/agents/AgentPermission.yaml index 4cef5cb03..4f0b768d8 100644 --- a/openapi/components/schemas/agents/AgentPermission.yaml +++ b/openapi/components/schemas/agents/AgentPermission.yaml @@ -1,14 +1,12 @@ type: string enum: - VIEW_TRANSACTIONS - - CREATE_TRANSFERS - CREATE_QUOTES - EXECUTE_QUOTES - MANAGE_EXTERNAL_ACCOUNTS description: >- Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. - CREATE_TRANSFERS: Can initiate same-currency transfers. - CREATE_QUOTES: Can create cross-currency quotes. - EXECUTE_QUOTES: Can execute cross-currency quotes. + CREATE_QUOTES: Can create quotes. + EXECUTE_QUOTES: Can execute quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts. diff --git a/openapi/components/schemas/agents/AgentTransferDetails.yaml b/openapi/components/schemas/agents/AgentTransferDetails.yaml deleted file mode 100644 index d8eb2f5c3..000000000 --- a/openapi/components/schemas/agents/AgentTransferDetails.yaml +++ /dev/null @@ -1,25 +0,0 @@ -type: object -description: Details of a transfer-type agent action (TRANSFER_OUT or TRANSFER_IN). -required: - - amount - - currency - - sourceAccountId - - destinationAccountId -properties: - amount: - type: integer - format: int64 - description: Transfer amount in the smallest unit of the specified currency. - example: 50000 - currency: - type: string - description: ISO 4217 currency code for the transfer amount. - example: USD - sourceAccountId: - type: string - description: ID of the source account (internal or external). - example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destinationAccountId: - type: string - description: ID of the destination account (internal or external). - example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index aba89ec4f..b99aa2d11 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -340,10 +340,6 @@ paths: $ref: paths/agents/agents_me_actions.yaml /agents/me/actions/{actionId}: $ref: paths/agents/agents_me_actions_{actionId}.yaml - /agents/me/transfer-in: - $ref: paths/agents/agents_me_transfer-in.yaml - /agents/me/transfer-out: - $ref: paths/agents/agents_me_transfer-out.yaml /agents/me/internal-accounts: $ref: paths/agents/agents_me_internal-accounts.yaml /agents/me/external-accounts: diff --git a/openapi/paths/agents/agents_me_transfer-in.yaml b/openapi/paths/agents/agents_me_transfer-in.yaml deleted file mode 100644 index 4deba5af0..000000000 --- a/openapi/paths/agents/agents_me_transfer-in.yaml +++ /dev/null @@ -1,85 +0,0 @@ -post: - summary: Create a transfer-in - description: > - Transfer funds from an external account to an internal account for the authenticated - agent's customer. Accounts must belong to the agent's customer. Requires the - CREATE_TRANSFERS permission in the agent's policy. - - If the agent's policy requires approval for this amount, the transaction will be - created in a pending state and must be approved by the platform via - `POST /agents/{agentId}/actions/{actionId}/approve`. - - This endpoint should only be used for external account sources with pull functionality - (e.g. ACH Pull). Otherwise, use the payment instructions on the internal account to - deposit funds. - operationId: agentCreateTransferIn - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: > - A unique identifier for the request. If the same key is sent multiple times, - the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/transfers/TransferInRequest.yaml - examples: - transferIn: - summary: Transfer from external to internal account - value: - source: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - destination: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - amount: 12550 - responses: - '201': - description: >- - Action submitted successfully. If the agent's policy requires approval, the returned - `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the - policy permits automatic execution, status will be `APPROVED` and `transaction` will - be populated. - content: - application/json: - schema: - $ref: ../../components/schemas/agents/AgentAction.yaml - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error403.yaml - '404': - description: Account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/agents/agents_me_transfer-out.yaml b/openapi/paths/agents/agents_me_transfer-out.yaml deleted file mode 100644 index 24ef251e8..000000000 --- a/openapi/paths/agents/agents_me_transfer-out.yaml +++ /dev/null @@ -1,81 +0,0 @@ -post: - summary: Create a transfer-out - description: > - Transfer funds from an internal account to an external account for the authenticated - agent's customer. Accounts must belong to the agent's customer. Requires the - CREATE_TRANSFERS permission in the agent's policy. - - If the agent's policy requires approval for this amount, the transaction will be - created in a pending state and must be approved by the platform via - `POST /agents/{agentId}/actions/{actionId}/approve`. - operationId: agentCreateTransferOut - tags: - - Agent Operations - security: - - AgentAuth: [] - parameters: - - name: Idempotency-Key - in: header - required: false - description: > - A unique identifier for the request. If the same key is sent multiple times, - the server will return the same response as the first request. - schema: - type: string - example: 550e8400-e29b-41d4-a716-446655440000 - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/transfers/TransferOutRequest.yaml - examples: - transferOut: - summary: Transfer from internal to external account - value: - source: - accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123 - destination: - accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - amount: 12550 - responses: - '201': - description: >- - Action submitted successfully. If the agent's policy requires approval, the returned - `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the - policy permits automatic execution, status will be `APPROVED` and `transaction` will - be populated. - content: - application/json: - schema: - $ref: ../../components/schemas/agents/AgentAction.yaml - '400': - description: Bad request - Invalid parameters - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '403': - description: Forbidden - Agent policy does not permit this operation or spending limit exceeded - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error403.yaml - '404': - description: Account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml From d8409a9f088a168cebc56d6061aefe6fb860b87d Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 00:33:22 +0000 Subject: [PATCH 4/9] docs: tighten the send-payment framing and drop the sandbox transfer-out stub Review feedback: Pick between one-step and two-step execution on whether your UX shows the customer rates or fees, not on whether the currencies match. A same-currency transfer has no exchange rate but can still carry fees worth surfacing, so the two-step flow is a reasonable choice there too. Restore the original "Monitor completion" wording on the last step. Scope the page description to what the page covers. It is not limited to internal-to-external, so say any combination of internal and external accounts in either direction. UMA destinations use the same endpoint but this page carries no UMA example, so link out to the global-p2p guide rather than implying coverage. Drop the sandbox "Transferring out funds" section. Once it stopped naming /transfer-out it was one sentence pointing at the transfer-in patterns, so fold that into the patterns note, which now says the suffixes govern the external account in either direction. The "## Transfer in" heading stays so the existing #transfer-in link keeps resolving. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- mintlify/api-reference/sandbox-testing.mdx | 9 +++--- .../payment-flow/send-payment.mdx | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/mintlify/api-reference/sandbox-testing.mdx b/mintlify/api-reference/sandbox-testing.mdx index dc2058aa3..4ec5ba518 100644 --- a/mintlify/api-reference/sandbox-testing.mdx +++ b/mintlify/api-reference/sandbox-testing.mdx @@ -33,10 +33,13 @@ In production, internal accounts are funded by sending a bank transfer to the ac ### Transfer in from an external account -Use the `/quotes` endpoint with `immediatelyExecute` set to `true` to pull funds from an external account into an internal account. The external account's number suffix determines the outcome: +Create a quote with the external account as the source and `immediatelyExecute` set to `true`. The external account's number suffix determines the outcome: +These suffixes govern any transfer touching the external account, in either direction — use +the same patterns to test pushing funds out to it. + ### Sandbox fund endpoint Instantly add funds to any internal account using `/sandbox/internal-accounts/{accountId}/fund`: @@ -75,10 +78,6 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/send \ }' ``` -## Transferring out funds - -Use the `/quotes` endpoint with `immediatelyExecute` set to `true` to push funds from an internal account to an external account in the same currency. The external account's number suffix controls the outcome using the same patterns as [transfer in](#transfer-in-from-an-external-account). - ## Sending to a UMA address diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index aeda8706e..0a6dc5280 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -1,13 +1,13 @@ --- title: "Sending Payments" -description: "Learn how to send payments from internal accounts to external bank accounts, with or without currency conversion" +description: "Learn how to send payments between accounts in either direction, with or without currency conversion" icon: "/images/icons/paper-plane-top-right.svg" "og:image": "/images/og/og-payouts-b2b.png" --- import { FeatureCard, FeatureCardGrid } from '/snippets/feature-card.mdx'; -Send payments from your customers' internal accounts to their external bank accounts or to other destinations, with automatic exchange rate handling when the currencies differ. +Send payments between any combination of internal and external accounts, in either direction, with automatic exchange rate handling when the currencies differ. ## Overview @@ -18,14 +18,19 @@ rate — and creates the transaction that carries the money. What varies is when you execute it: - **In one request.** Set `immediatelyExecute` and Grid creates and executes the quote - together. This is the usual choice for a same-currency transfer, where there is no rate - to weigh up. -- **In two steps.** Create the quote, check the rate and fees, then call execute before the - quote expires. This is the usual choice when converting currency. + together. Use this when you don't need to put rate or fee details in front of your user + before the money moves. +- **In two steps.** Create the quote, show your user what the transfer will cost, then call + execute before the quote expires. Use this whenever your UX surfaces rates or fees — which + includes same-currency transfers, where there is no exchange rate but there can still be + fees worth showing. Either way the request shape is the same, and the payment rail is chosen from the destination account. +The same endpoint sends to UMA addresses by giving the quote a `UMA_ADDRESS` destination. +See [Sending payments](/global-p2p/sending-receiving-payments/sending-payments) for that flow. + ## Prerequisites Before sending payments, ensure you have: @@ -118,8 +123,9 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ **Same-currency transfers use this exact request.** The two currencies simply match, and - the quote comes back with an `exchangeRate` of `1`. Add `"immediatelyExecute": true` to - create and execute in this one request, and skip the next two steps. + the quote comes back with an `exchangeRate` of `1` — the fee fields are still populated. + Add `"immediatelyExecute": true` to create and execute in this one request and skip the + next two steps. @@ -159,8 +165,9 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ - For a same-currency transfer there is no rate to weigh up, so this step is usually skipped - in favor of `immediatelyExecute`. + Skip this step by setting `immediatelyExecute` on the quote. A same-currency quote has no + exchange rate to review, but check `feesIncluded` if your UX shows the customer what the + transfer costs. @@ -184,8 +191,8 @@ creation. - - The quote's `transactionId` identifies the transaction that carries the payment. It progresses through `PENDING` → `PROCESSING` → `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as it does: + + After execution, a transaction is created and progresses through `PENDING` → `PROCESSING` → `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses: ```json { From e41dd69545cc0c2d97fa7599e8c00c408da1376a Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 00:39:22 +0000 Subject: [PATCH 5/9] docs: drop the sandbox transfer sections in favor of funding and outcome patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sandbox pages framed their content around transfer-in and transfer-out, which stopped being API concepts. On the API reference sandbox page, the "Transfer in" section opened by re-explaining how internal accounts get funded in production. Internal Accounts already covers that — the list endpoints, the funding payment instructions, and how to display them to a customer — so link there instead. What is genuinely sandbox-only is the fund endpoint, which is now the body of a "Funding an internal account" section, plus a line on using a quote to exercise the pull path. Moved the suffix table up under "Adding external accounts", next to the sentence that already tells you the last 3 digits pick the scenario. The suffix is a property of the account, not of a direction of travel. The payouts sandbox page had the same split: two POST /quotes blocks differing only in which side held the external account, with the suffix table between them. Now one "Testing Transfer Outcomes" section with the table first and a single example, noting you swap the two accountId values to test the other direction. Repointed the one inbound #transfer-in link. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- mintlify/api-reference/sandbox-testing.mdx | 29 +++++++-------- .../platform-tools/sandbox-testing.mdx | 35 +++++-------------- 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/mintlify/api-reference/sandbox-testing.mdx b/mintlify/api-reference/sandbox-testing.mdx index 4ec5ba518..0258df9ce 100644 --- a/mintlify/api-reference/sandbox-testing.mdx +++ b/mintlify/api-reference/sandbox-testing.mdx @@ -23,26 +23,20 @@ The Grid sandbox environment simulates real payment flows without moving real mo -### Beneficiary name verification - - - -## Transfer in - -In production, internal accounts are funded by sending a bank transfer to the account's payment instructions or by pulling from an external account. In sandbox, you have two options: - -### Transfer in from an external account + -Create a quote with the external account as the source and `immediatelyExecute` set to `true`. The external account's number suffix determines the outcome: +These outcomes apply whenever the account is used, as the source of a pull or the +destination of a payout. - +### Beneficiary name verification -These suffixes govern any transfer touching the external account, in either direction — use -the same patterns to test pushing funds out to it. + -### Sandbox fund endpoint +## Funding an internal account -Instantly add funds to any internal account using `/sandbox/internal-accounts/{accountId}/fund`: +Internal accounts are funded from their payment instructions or by pulling from an external +account — see [Internal Accounts](/payouts-and-b2b/depositing-funds/internal-accounts) for +both. Sandbox adds a shortcut that skips the wait: ```bash curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-accounts/{accountId}/fund \ @@ -51,6 +45,9 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-account -d '{ "amount": 100000 }' ``` +To exercise the pull path instead, create a quote with an external account as the source and +`immediatelyExecute` set to `true`. The account's suffix decides the outcome. + ## Creating quotes @@ -59,7 +56,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/sandbox/internal-account After creating a quote, you need to fund it to trigger execution. There are two ways to do this in sandbox: -**Prefunded internal account** — If your quote's source is an internal account, fund the account using one of the methods described in [transfer in](#transfer-in), then call the quote execute endpoint to trigger the transaction: +**Prefunded internal account** — If your quote's source is an internal account, fund the account using one of the methods described in [funding an internal account](#funding-an-internal-account), then call the quote execute endpoint to trigger the transaction: ```bash curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/{quoteId}/execute \ diff --git a/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx b/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx index 621eda018..7bea16655 100644 --- a/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx +++ b/mintlify/payouts-and-b2b/platform-tools/sandbox-testing.mdx @@ -65,33 +65,15 @@ Alternatively, you can also fund internal accounts using the `/quotes` endpoint -### Testing Transfer-In (Pull from External Account) +### Testing Transfer Outcomes -When you create a quote whose source is an external account created using test patterns, the transfer will complete instantly in sandbox with the behavior determined by the account number: - -```bash -POST /quotes - -{ - "source": { - "sourceType": "ACCOUNT", - "accountId": "ExternalAccount:abc123" // Uses test pattern from creation - }, - "destination": { - "destinationType": "ACCOUNT", - "accountId": "InternalAccount:xyz789" - }, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 10000, // $100 in cents - "immediatelyExecute": true -} -``` +The external account's test pattern determines the outcome whenever that account is used, +whether you are pushing funds to it or pulling funds from it: -### Testing Transfer-Out (Push to External Account) - -Transfer-out works the same way - the destination external account's test pattern determines the outcome: +Create a quote with the external account on the side you want to test and +`immediatelyExecute` set to `true`: ```bash POST /quotes @@ -103,15 +85,16 @@ POST /quotes }, "destination": { "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:abc123" // Uses test pattern + "accountId": "ExternalAccount:abc123" // Uses test pattern from creation }, "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 10000, + "lockedCurrencyAmount": 10000, // $100 in cents "immediatelyExecute": true } ``` -The transfer will instantly simulate the bank transfer process and complete with the appropriate status based on the external account's test pattern. +Swap the two `accountId` values to pull from the external account instead. Either way the +transfer completes instantly in sandbox with the status the pattern dictates. ## Testing Cross-Currency Quotes From 3f6d3b60797da521d289ee932216404a7d148cd8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 15:19:07 +0000 Subject: [PATCH 6/9] docs: trim the send-payment intro and status cross-reference Review feedback. Shorten the page description to "Learn how to send payments between accounts", drop "any combination of" and "in either direction" from the intro sentence, and cut the "like bank returns" example from the pointer to the transaction lifecycle guide. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- mintlify/payouts-and-b2b/payment-flow/send-payment.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index 0a6dc5280..c4dd686a9 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -1,13 +1,13 @@ --- title: "Sending Payments" -description: "Learn how to send payments between accounts in either direction, with or without currency conversion" +description: "Learn how to send payments between accounts" icon: "/images/icons/paper-plane-top-right.svg" "og:image": "/images/og/og-payouts-b2b.png" --- import { FeatureCard, FeatureCardGrid } from '/snippets/feature-card.mdx'; -Send payments between any combination of internal and external accounts, in either direction, with automatic exchange rate handling when the currencies differ. +Send payments between internal and external accounts, with automatic exchange rate handling when the currencies differ. ## Overview @@ -237,7 +237,7 @@ If a transaction fails, Grid initiates a refund automatically. You'll receive `O | `EXPIRED` | Quote wasn't executed before the expiry window | -For the full state diagram including refund tracking and edge cases like bank returns, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. +For the full state diagram including refund tracking and edge cases, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. ## Checking Payment Status From e57eca31ccdde3a95d48d9b9b6f2b23081cc3578 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 20:30:24 +0000 Subject: [PATCH 7/9] docs: note the SCA branch on execute, drop the same-currency lifecycle section Review feedback. The execute step claimed the quote always comes back PROCESSING. Where SCA applies it does not: the endpoint's own 200 description says the transfer is not initiated, the quote returns PENDING_AUTHORIZATION with an scaChallenge, and re-calling execute returns 409. An integration following the old text would have polled a transaction that never moves. Added the branch and the authorize call. Deleted the Same-Currency Transfers section from the transaction lifecycle page. It said same-currency follows the lifecycle above unchanged, which is the argument for not having a section. Its one concrete detail, the pullable external account requirement, is covered on the account model and quote system pages among others. Nothing links to the removed anchor. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .../payouts-and-b2b/payment-flow/send-payment.mdx | 11 +++++++++-- .../core-concepts/transaction-lifecycle.mdx | 11 ----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index c4dd686a9..7ca6b9311 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -179,8 +179,15 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" ``` -The quote comes back with `status` `PROCESSING` and the same `transactionId` it carried at -creation. +When SCA does not apply, the quote comes back with `status` `PROCESSING` and the same +`transactionId` it carried at creation. + + + **Strong Customer Authentication.** Where SCA applies (currently EU customers), execute + does **not** initiate the transfer. The quote returns with `PENDING_AUTHORIZATION` and an + `scaChallenge`; release the transfer by authorizing it with + `POST /quotes/{quoteId}/authorize`. Re-calling execute returns 409. + Once executed, the quote creates a transaction and the transfer begins processing. The `transactionId` can be used to track the payment. diff --git a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx index ee0f5eef0..6eb992ed3 100644 --- a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx +++ b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx @@ -72,17 +72,6 @@ Understanding the transaction lifecycle helps you build robust payment flows, ha Most transactions on Grid are completed in seconds. -## Same-Currency Transfers - -Same-currency transfers go through `/quotes` and follow the lifecycle above -unchanged. Setting `immediatelyExecute` to `true` collapses the create and execute -steps into one request; the quote's `transactionId` identifies the resulting -transaction. Pulling from an external account into an internal one works the same -way, and requires a "pullable" external account (e.g., debit cards). - -See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#send-a-payment) -for a worked example. - ## Outgoing Payment Status A single `status` field represents whether the transaction reached its destination: From 940c652da8de9aaf5690a533a8f735ddfc4ae75d Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 20:35:30 +0000 Subject: [PATCH 8/9] docs: defer the SCA mechanics to the quote system guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The warning restated what quote-system.mdx already covers under Strong Customer Authentication, down to the 409 on re-calling execute. Kept the correction it carried — execute does not always come back PROCESSING — as one clause with a link, rather than a second copy of the mechanics. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .../payouts-and-b2b/payment-flow/send-payment.mdx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx index 7ca6b9311..261bf3656 100644 --- a/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx +++ b/mintlify/payouts-and-b2b/payment-flow/send-payment.mdx @@ -179,15 +179,10 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" ``` -When SCA does not apply, the quote comes back with `status` `PROCESSING` and the same -`transactionId` it carried at creation. - - - **Strong Customer Authentication.** Where SCA applies (currently EU customers), execute - does **not** initiate the transfer. The quote returns with `PENDING_AUTHORIZATION` and an - `scaChallenge`; release the transfer by authorizing it with - `POST /quotes/{quoteId}/authorize`. Re-calling execute returns 409. - +The quote comes back with `status` `PROCESSING` and the same `transactionId` it carried at +creation — unless the customer requires +[Strong Customer Authentication](/platform-overview/core-concepts/quote-system), in which +case it returns `PENDING_AUTHORIZATION` and the transfer waits on that. Once executed, the quote creates a transaction and the transfer begins processing. The `transactionId` can be used to track the payment. From 583b40fd5b795350b7b95c7f62cb0f0c63610d49 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 Aug 2026 20:50:08 +0000 Subject: [PATCH 9/9] docs: merge the same-currency and cross-currency sending snippets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback from @pengying: the same-currency distinction no longer earns its own section now that both go through POST /quotes. Deleting the snippet outright would have lost content, though. cross-currency.mdx covered none of immediatelyExecute, paymentRail or remittanceInformation, and remittanceInformation appears nowhere else in global-p2p — so a reader there would have lost the 80-character reference that rides along on ACH Addenda, FedNow/RTP, and wire OBI. Merged instead. cross-currency.mdx becomes accounts.mdx, covering any payment to an internal or external account with or without conversion. Its fork is one-step versus two-step execution, matching send-payment.mdx. Folded in the three fields above, and noted on the review step that immediatelyExecute skips it. Split "Funding with cryptocurrencies" into crypto-funding.mdx. It is about how a quote is funded rather than where it is sent, and it was the largest thing in the file. The global-p2p page dropped from three methods to two: to an account, or to an UMA address. Nothing linked to the retired anchors. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TxsyZEDjQDt97rR3kd2kxv --- .../sending-payments.mdx | 18 +- .../{cross-currency.mdx => accounts.mdx} | 165 ++++-------------- mintlify/snippets/sending/crypto-funding.mdx | 125 +++++++++++++ mintlify/snippets/sending/same-currency.mdx | 114 ------------ 4 files changed, 164 insertions(+), 258 deletions(-) rename mintlify/snippets/sending/{cross-currency.mdx => accounts.mdx} (56%) create mode 100644 mintlify/snippets/sending/crypto-funding.mdx delete mode 100644 mintlify/snippets/sending/same-currency.mdx diff --git a/mintlify/global-p2p/sending-receiving-payments/sending-payments.mdx b/mintlify/global-p2p/sending-receiving-payments/sending-payments.mdx index d8e1b6b9d..450228c03 100644 --- a/mintlify/global-p2p/sending-receiving-payments/sending-payments.mdx +++ b/mintlify/global-p2p/sending-receiving-payments/sending-payments.mdx @@ -3,25 +3,23 @@ title: "Sending Payments" icon: "/images/icons/paper-plane-top-right.svg" "og:image": "/images/og/og-global-p2p.png" --- -This guide covers three methods to send payments: +Every payment goes through `POST /quotes`. What changes is where you are sending: -1. Same-currency transfer to an external account -2. Cross-currency transfer with a quote -3. Sending to an UMA address +1. To an account — internal or external, with or without currency conversion +2. To an UMA address ## Choosing the right method -- **Same-currency**: Best for domestic payouts when sender and recipient use the same currency. Uses local payment rails (e.g., RTP, SEPA Instant, PIX, FPS) for low cost and fast settlement. -- **Cross-currency**: Use when conversion is required or when paying globally across borders. Also supports sending to a crypto wallet address when configured. +- **Account**: Pay an internal or external account. Grid converts when the currencies differ and settles over local payment rails (e.g., ACH, RTP, SEPA Instant, PIX, FPS) when they match. Also covers sending to a crypto wallet address when configured. - **UMA**: Send using a Universal Money Address. Ideal for global counterparties on networks. -import SameCurrency from '/snippets/sending/same-currency.mdx' +import SendToAccount from '/snippets/sending/accounts.mdx' - + -import CrossCurrency from '/snippets/sending/cross-currency.mdx' +import CryptoFunding from '/snippets/sending/crypto-funding.mdx' - + import SendUMA from '/snippets/sending/uma.mdx' diff --git a/mintlify/snippets/sending/cross-currency.mdx b/mintlify/snippets/sending/accounts.mdx similarity index 56% rename from mintlify/snippets/sending/cross-currency.mdx rename to mintlify/snippets/sending/accounts.mdx index 74ebeb2ef..c273e6bb2 100644 --- a/mintlify/snippets/sending/cross-currency.mdx +++ b/mintlify/snippets/sending/accounts.mdx @@ -1,12 +1,18 @@ -## Cross-Currency Transfers +## Sending to an Account -Use the quotes flow when sending funds with currency conversion. This locks in an exchange rate and provides all details needed to execute the transfer. +Every payment to an internal or external account goes through `POST /quotes`, whether or not +the currencies differ. The quote prices the transfer — amounts, fees, and, when converting, +a locked exchange rate — and creates the transaction that carries the money. -### When to use cross-currency transfers +What varies is when you execute it: -- Converting USD, USDC, USDT to EUR, MXN, BRL, BTC, or other supported fiat and crypto currencies -- Sending international payments with automatic currency conversion -- Need to lock in a specific exchange rate for the transfer +- **In one request.** Set `immediatelyExecute` and Grid creates and executes the quote + together. Use this when you don't need to put rate or fee details in front of your user + before the money moves. +- **In two steps.** Create the quote, show your user what the transfer will cost, then call + execute before the quote expires. Use this whenever your UX surfaces rates or fees — which + includes same-currency transfers, where there is no exchange rate but there can still be + fees worth showing. ### Create and execute a quote @@ -20,9 +26,10 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ -H 'Content-Type: application/json' \ -d '{ "source": { "sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" }, - "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, + "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", "paymentRail": "ACH" }, "lockedCurrencySide": "SENDING", "lockedCurrencyAmount": 10000, + "remittanceInformation": "INV-12345", "description": "Payment for services - Invoice #1234" }' ``` @@ -47,12 +54,27 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ } ``` + + **Same-currency transfers use this exact request.** The two currencies simply match, and + the quote comes back with an `exchangeRate` of `1` — the fee fields are still populated. + Add `"immediatelyExecute": true` to create and execute in this one request and skip the + next two steps. + + **Locked currency side** determines which amount is fixed: - `SENDING`: Lock the sending amount (receiving amount calculated based on exchange rate) - `RECEIVING`: Lock the receiving amount (sending amount calculated based on exchange rate) + + The `paymentRail` field is optional. If omitted, Grid selects a default rail for the destination. Specify a rail (e.g., `ACH`, `WIRE`, `RTP`, `FEDNOW`) when you need to control which payment network processes the transfer. + + + + `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail. + + For external account or UMA destinations, some payment corridors require a purpose of payment. Include the `purposeOfPayment` field in the quote request: @@ -85,7 +107,8 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ - Before executing, review the quote to ensure: + Skip this step and the next by setting `immediatelyExecute` on the quote. Otherwise, + before executing, review the quote to ensure: - Exchange rate is acceptable - Fees are as expected @@ -169,129 +192,3 @@ If a transaction fails, Grid initiates a refund automatically. You'll receive `O - -### Funding with cryptocurrencies - -Cross-currency transfers support funding via USDC and BTC on popular blockchains including Solana, Base, Lightning and Spark. When you create a quote specifying the source currency as USDC or BTC, the response includes payment instructions for multiple funding options. - -#### Supported blockchains - -| Blockchain Network | Cryptocurrencies | -|-------|-------------| -| Solana | USDC | -| Base | USDC | -| Tron | USDT | -| Polygon | USDC | -| Ethereum | USDC, USDT | -| Lightning | BTC | -| Spark | BTC | - -#### Create a quote for USDC-funded transfer - -Request a quote that provides blockchain funding options: - -```bash -curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ - -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ - -H 'Content-Type: application/json' \ - -d '{ - "source": { - "sourceType": "REALTIME_FUNDING", - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "currency": "USDC" - }, - "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 10000, - "description": "Payment for services - Invoice #1234" - }' -``` - -The response includes an array of payment instructions, including blockchain wallet addresses for USDC and invoices for BTC: - -```json Success (201 Created) -{ - "id": "Quote:019542f5-b3e7-1d02-0000-000000000025", - "status": "PENDING", - "createdAt": "2025-10-03T15:00:00Z", - "expiresAt": "2025-10-03T15:15:00Z", - "source": { "sourceType": "REALTIME_FUNDING", "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", "currency": "USDC" }, - "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, - "sendingCurrency": { "code": "USDC", "name": "USD Coin", "symbol": "USDC", "decimals": 2 }, - "receivingCurrency": { "code": "EUR", "name": "Euro", "symbol": "€", "decimals": 2 }, - "totalSendingAmount": 10000, - "totalReceivingAmount": 9200, - "exchangeRate": 0.92, - "feesIncluded": 50, - "platformFeesIncluded": 0, - "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000030", - "paymentInstructions": [ - { - "accountOrWalletInfo": { - "accountType": "SOLANA_WALLET", - "assetType": "USDC", - "address": "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg" - } - }, - { - "accountOrWalletInfo": { - "accountType": "BASE_WALLET", - "assetType": "USDC", - "address": "0x1234567890abcdef1234567890abcdef12345678" - } - } - ] -} -``` - -#### Transaction processing - -Grid automatically detects blockchain deposits and processes the transfer once funds are received: - - - - Transfer the exact amount of USDC specified in `totalSendingAmount` to your chosen blockchain wallet address. - - - - Grid monitors the blockchain for incoming deposits. You'll receive an `INTERNAL_ACCOUNT.BALANCE_UPDATED` webhook when the deposit is confirmed: - -```json -{ - "id": "Webhook:019542f5-b3e7-1d02-0000-000000000040", - "type": "INTERNAL_ACCOUNT.BALANCE_UPDATED", - "timestamp": "2025-10-03T15:05:00Z", - "data": { - "id": "InternalAccount:019542f5-b3e7-1d02-0000-000000000025", - "balance": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } }, - "totalBalance": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } } - } -} -``` - - - - Once the deposit is confirmed, Grid executes the cross-currency transfer. You'll receive `OUTGOING_PAYMENT.` webhooks as the transfer progresses: - -```json -{ - "id": "Webhook:019542f5-b3e7-1d02-0000-000000000041", - "type": "OUTGOING_PAYMENT.COMPLETED", - "timestamp": "2025-10-03T15:31:00Z", - "data": { - "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030", - "status": "COMPLETED", - "type": "OUTGOING", - "direction": "DEBIT", - "sentAmount": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } }, - "receivedAmount": { "amount": 9200, "currency": { "code": "EUR", "decimals": 2 } }, - "exchangeRate": 0.92, - "settledAt": "2025-10-03T15:30:00Z", - "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000025" - } -} -``` - -See the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide for all possible status transitions and refund handling. - - diff --git a/mintlify/snippets/sending/crypto-funding.mdx b/mintlify/snippets/sending/crypto-funding.mdx new file mode 100644 index 000000000..dbc9a01c8 --- /dev/null +++ b/mintlify/snippets/sending/crypto-funding.mdx @@ -0,0 +1,125 @@ +## Funding with cryptocurrencies + +Transfers can be funded via USDC and BTC on popular blockchains including Solana, Base, Lightning and Spark. When you create a quote specifying the source currency as USDC or BTC, the response includes payment instructions for multiple funding options. + +### Supported blockchains + +| Blockchain Network | Cryptocurrencies | +|-------|-------------| +| Solana | USDC | +| Base | USDC | +| Tron | USDT | +| Polygon | USDC | +| Ethereum | USDC, USDT | +| Lightning | BTC | +| Spark | BTC | + +### Create a quote for USDC-funded transfer + +Request a quote that provides blockchain funding options: + +```bash +curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ + -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ + -H 'Content-Type: application/json' \ + -d '{ + "source": { + "sourceType": "REALTIME_FUNDING", + "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", + "currency": "USDC" + }, + "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, + "lockedCurrencySide": "SENDING", + "lockedCurrencyAmount": 10000, + "description": "Payment for services - Invoice #1234" + }' +``` + +The response includes an array of payment instructions, including blockchain wallet addresses for USDC and invoices for BTC: + +```json Success (201 Created) +{ + "id": "Quote:019542f5-b3e7-1d02-0000-000000000025", + "status": "PENDING", + "createdAt": "2025-10-03T15:00:00Z", + "expiresAt": "2025-10-03T15:15:00Z", + "source": { "sourceType": "REALTIME_FUNDING", "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", "currency": "USDC" }, + "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, + "sendingCurrency": { "code": "USDC", "name": "USD Coin", "symbol": "USDC", "decimals": 2 }, + "receivingCurrency": { "code": "EUR", "name": "Euro", "symbol": "€", "decimals": 2 }, + "totalSendingAmount": 10000, + "totalReceivingAmount": 9200, + "exchangeRate": 0.92, + "feesIncluded": 50, + "platformFeesIncluded": 0, + "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000030", + "paymentInstructions": [ + { + "accountOrWalletInfo": { + "accountType": "SOLANA_WALLET", + "assetType": "USDC", + "address": "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg" + } + }, + { + "accountOrWalletInfo": { + "accountType": "BASE_WALLET", + "assetType": "USDC", + "address": "0x1234567890abcdef1234567890abcdef12345678" + } + } + ] +} +``` + +### Transaction processing + +Grid automatically detects blockchain deposits and processes the transfer once funds are received: + + + + Transfer the exact amount of USDC specified in `totalSendingAmount` to your chosen blockchain wallet address. + + + + Grid monitors the blockchain for incoming deposits. You'll receive an `INTERNAL_ACCOUNT.BALANCE_UPDATED` webhook when the deposit is confirmed: + +```json +{ + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000040", + "type": "INTERNAL_ACCOUNT.BALANCE_UPDATED", + "timestamp": "2025-10-03T15:05:00Z", + "data": { + "id": "InternalAccount:019542f5-b3e7-1d02-0000-000000000025", + "balance": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } }, + "totalBalance": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } } + } +} +``` + + + + Once the deposit is confirmed, Grid executes the cross-currency transfer. You'll receive `OUTGOING_PAYMENT.` webhooks as the transfer progresses: + +```json +{ + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000041", + "type": "OUTGOING_PAYMENT.COMPLETED", + "timestamp": "2025-10-03T15:31:00Z", + "data": { + "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030", + "status": "COMPLETED", + "type": "OUTGOING", + "direction": "DEBIT", + "sentAmount": { "amount": 10000, "currency": { "code": "USDC", "decimals": 2 } }, + "receivedAmount": { "amount": 9200, "currency": { "code": "EUR", "decimals": 2 } }, + "exchangeRate": 0.92, + "settledAt": "2025-10-03T15:30:00Z", + "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000025" + } +} +``` + +See the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide for all possible status transitions and refund handling. + + diff --git a/mintlify/snippets/sending/same-currency.mdx b/mintlify/snippets/sending/same-currency.mdx deleted file mode 100644 index dc8344076..000000000 --- a/mintlify/snippets/sending/same-currency.mdx +++ /dev/null @@ -1,114 +0,0 @@ -## Same-Currency Transfers - -Use the `/quotes` endpoint when sending funds in the same currency (no exchange rate needed). Quotes cover same-currency and cross-currency transfers alike, so one integration handles both. - -### When to use same-currency transfers - -- Transferring USD from a USD internal account to a USD external account -- Sending funds within the same country using the same payment rail -- No currency conversion is required - -### Create a transfer - - - - Retrieve the internal account (source) and external account (destination) IDs: - -```bash -curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001' \ - -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" -``` - -Note the `id` fields from both the internal and external accounts you want to use. - - - - - Create the quote by specifying the source and destination accounts. Set `immediatelyExecute` to `true` to move the funds in the same request: - -```bash -curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \ - -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ - -H 'Content-Type: application/json' \ - -d '{ - "source": { "sourceType": "ACCOUNT", "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" }, - "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965", "paymentRail": "ACH" }, - "lockedCurrencySide": "SENDING", - "lockedCurrencyAmount": 12550, - "immediatelyExecute": true, - "remittanceInformation": "INV-12345" - }' -``` - - - The `paymentRail` field is optional. If omitted, Grid selects a default rail for the destination. - - - - `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail. - - - - `lockedCurrencySide` tells Grid which side of the transfer the amount applies to. For a same-currency transfer the two sides are equal, so `SENDING` is the natural choice. - - -```json Success (201 Created) -{ - "id": "Quote:019542f5-b3e7-1d02-0000-000000000006", - "status": "PROCESSING", - "createdAt": "2025-10-03T15:00:00Z", - "expiresAt": "2025-10-03T15:05:00Z", - "source": { - "sourceType": "ACCOUNT", - "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" - }, - "destination": { - "destinationType": "ACCOUNT", - "accountId": "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" - }, - "sendingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 }, - "receivingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 }, - "totalSendingAmount": 12550, - "totalReceivingAmount": 12550, - "exchangeRate": 1, - "feesIncluded": 0, - "platformFeesIncluded": 0, - "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000015" -} -``` - - - Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents $125.50 USD. - - - - To review the fees before any funds move, omit `immediatelyExecute` (it defaults to `false`), inspect the returned quote, and then call `POST /quotes/{quoteId}/execute` before `expiresAt`. Same-currency transfers have no exchange rate to review, so immediate execution is usually what you want. - - - - - The quote's `transactionId` identifies the transaction that carries the payment. It is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.` webhooks as the transaction progresses. The webhook body contains the full transaction resource: - -```json -{ - "type": "OUTGOING_PAYMENT.COMPLETED", - "data": { - "id": "Transaction:019542f5-b3e7-1d02-0000-000000000015", - "status": "COMPLETED", - "type": "OUTGOING", - "direction": "DEBIT", - "sentAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "receivedAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } }, - "settledAt": "2025-10-03T15:02:30Z" - }, - "timestamp": "2025-10-03T15:03:00Z" -} -``` - -If a transaction fails, Grid initiates a refund automatically. You'll receive `OUTGOING_PAYMENT.REFUND_PENDING` followed by `OUTGOING_PAYMENT.REFUND_COMPLETED` or `OUTGOING_PAYMENT.REFUND_FAILED`. The transaction's `refund` object tracks the refund status and reference. - - - For the full state diagram, refund object details, and all webhook scenarios (including bank returns and manual cancellations), see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide. - - -