diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 68cc0393..6b14608c 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -323,6 +323,10 @@ export default defineConfig({
label: 'Accept a one-time payment for an online purchase',
link: '/guides/accept-otp-online-purchase/'
},
+ {
+ label: 'Send a remittance payment with fixed debit amount',
+ link: '/guides/onetime-remittance-fixed-debit'
+ },
{
label: 'Send a remittance payment with a fixed receive amount',
link: '/guides/onetime-remittance-fixed-receive/'
diff --git a/docs/src/content/docs/guides/onetime-remittance-fixed-debit.mdx b/docs/src/content/docs/guides/onetime-remittance-fixed-debit.mdx
new file mode 100644
index 00000000..273a890a
--- /dev/null
+++ b/docs/src/content/docs/guides/onetime-remittance-fixed-debit.mdx
@@ -0,0 +1,515 @@
+---
+title: Send a remittance with fixed debit amount
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import { Tabs, TabItem, Badge } from '@astrojs/starlight/components'
+import StartInteraction from '/src/partials/grant-start-interaction.mdx'
+import FinishInteraction from '/src/partials/grant-finish-interaction.mdx'
+
+:::tip[Summary]
+Learn how to send a one-time remittance payment by debiting the sender's account by a specific amount.
+:::
+
+A remittance payment is a transfer of money from one person to another, typically across borders or long distances, often involving currency conversion. In this guide, you will learn how to implement a one-time remittance payment feature where your users can specify exactly how much they want to send, rather than how much the recipient should receive.
+
+This approach is particularly useful for remittance app scenarios where:
+
+- Your users want to pay a fixed amount from their account
+- The recipient receives whatever amount remains after currency conversion
+- Your users want to avoid the complexity of calculating conversion fees upfront
+
+### Scenario
+
+Imagine someone in the US sending money to a family member in Mexico. They want to send exactly $100 US Dollars (USD) from their account, regardless of how much their family member actually receives after exchange rates are applied. This is different from where the sender specifies exactly how much the recipient should receive.
+
+For this guide, you'll assume the role of a developer building a remittance app. The guide explains how to send a \$100 USD payment, where the sender pays exactly \$100, and the recipient receives the amount in Mexican Pesos (MXN) after currency conversion.
+
+**Example transaction details:**
+
+- **Sender pays**: $100.00 USD (exact amount)
+- **Currency conversion**: USD to MXN at 17.00 exchange rate
+- **Recipient receives**: $1,700 MXN ($100 × 17.00)
+
+The three parties involved in this scenario are:
+
+- **Developer**: you, the person building the remittance app
+- **Sender**: the person using your app to send money in USD
+- **Recipient**: the person receiving the money in MXN
+
+Remember, Open Payments doesn't execute payments or touch money in any way. It's used to issue payment instructions before any money movement occurs. An example of a payment instruction is, "debit exactly \$100 from the sender's account and send the amount to the recipient's account after exchange rates are applied".
+
+## Endpoints
+
+- Get Wallet Address
+- Grant Request
+- Create Incoming Payment
+- Create a Quote
+- Create an Outgoing Payment
+- Complete Incoming Payment API
+
+## Steps
+
+### 1. Get wallet address information
+
+When the sender initiates a payment through your remittance app, you need to get wallet address information for both the sender and the recipient.
+
+Let's assume the sender has already provided their own wallet address when they signed up to use your app. Let's also assume the sender entered the recipient's wallet address into your app's payment form when initiating the payment.
+
+Call the [Get Wallet Address API](/apis/wallet-address-server/operations/get-wallet-address) for each address.
+
+
+
+ ```ts
+ const senderWalletAddress = await client.walletAddress.get({
+ url: 'https://cloudninebank.example.com/sender'
+ })
+ const recipientWalletAddress = await client.walletAddress.get({
+ url: 'https://happylifebank.example.com/recipient'
+ })
+```
+
+
+
+
+Example responses
+The following example shows a response from the sender's wallet provider.
+```json wrap
+{
+ "id": "https://cloudninebank.example.com/sender",
+ "assetCode": "USD",
+ "assetScale": 2,
+ "authServer": "https://auth.cloudninebank.example.com/",
+ "resourceServer": "https://cloudninebank.example.com/op"
+}
+```
+The following example shows a response from the recipient's wallet provider.
+
+```json wrap
+{
+ "id": "https://happylifebank.example.com/recipient",
+ "assetCode": "MXN",
+ "assetScale": 2,
+ "authServer": "https://auth.happylifebank.example.com/",
+ "resourceServer": "https://happylifebank.example.com/op"
+}
+```
+
+
+
+### 2. Request an incoming payment grant
+
+Use the recipient's `authServer` details, received in the previous step, to call the [Grant Request API](/apis/auth-server/operations/post-request).
+
+This call obtains an access token that allows your app to request that an incoming payment resource be created on the recipient's wallet account.
+
+
+
+ ```ts wrap
+ const recipientIncomingPaymentGrant = await client.grant.request(
+ {
+ url: recipientWalletAddress.authServer
+ },
+ {
+ access_token: {
+ access: [
+ {
+ type: "incoming-payment",
+ actions: ["create", "complete"],
+ },
+ ],
+ },
+ },
+ );
+````
+
+
+
+
+Example response
+The following shows an example response from the recipient's wallet provider.
+
+```json wrap
+{
+ "access_token": {
+ "value": "...", // access token value for incoming payment grant
+ "manage": "https://auth.happylifebank.example.com/token/{...}", // management uri for access token
+ "access": [
+ {
+ "type": "incoming-payment",
+ "actions": ["create", "complete"]
+ }
+ ]
+ },
+ "continue": {
+ "access_token": {
+ "value": "..." // access token for continuing the request
+ },
+ "uri": "https://auth.happylifebank.example.com/continue/{...}" // continuation request uri
+ }
+}
+```
+
+
+
+### 3. Request the creation of an incoming payment resource
+
+Use the access token returned in the previous response to call the [Create Incoming Payment API](/apis/resource-server/operations/create-incoming-payment).
+
+This call requests an incoming payment resource be created on the recipient's wallet account.
+
+
+
+ ```ts wrap
+ const recipientIncomingPayment = await client.incomingPayment.create(
+ {
+ url: recipientWalletAddress.resourceServer,
+ accessToken: recipientIncomingPaymentGrant.access_token.value
+ },
+ {
+ walletAddress: recipientWalletAddress.id
+ },
+ )
+```
+
+
+
+
+Example response
+The following shows an example response from the recipient's wallet provider.
+
+```json wrap
+{
+ "id": "https://happylifebank.example.com/incoming-payments/{...}",
+ "walletAddress": "https://happylifebank.example.com/recipient",
+ "receivedAmount": {
+ "value": "0",
+ "assetCode": "USD",
+ "assetScale": 2
+ },
+ "completed": false,
+ "createdAt": "2025-03-12T23:20:50.52Z",
+ "methods": [
+ {
+ "type": "ilp",
+ "ilpAddress": "...",
+ "sharedSecret": "..."
+ }
+ ]
+}
+```
+
+
+
+### 4. Request a quote grant
+
+Use the sender's `authServer` details, received in Step 1, to call the [Grant Request API](/apis/auth-server/operations/post-request).
+
+This call obtains an access token that allows your app to request that a quote resource be created on the sender's wallet account.
+
+
+
+ ```ts wrap
+ const senderQuoteGrant = await client.grant.request(
+ {
+ url: senderWalletAddress.authServer
+ },
+ {
+ access_token: {
+ access: [
+ {
+ type: 'quote',
+ actions: ['create']
+ }
+ ]
+ }
+ }
+ )
+ ```
+
+
+
+
+ Example response
+
+```json wrap
+{
+ "access_token": {
+ "value": "...", // access token value for quote grant
+ "manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token
+ "access": [
+ {
+ "type": "quote",
+ "actions": ["create"]
+ }
+ ]
+ },
+ "continue": {
+ "access_token": {
+ "value": "..." // access token for continuing the request
+ },
+ "uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri
+ }
+}
+```
+
+
+
+### 5. Request the creation of a quote resource
+
+Use the access token received in the previous step to call the [Create Quote API](/apis/resource-server/operations/create-quote).
+
+This call requests that a quote resource be created on the sender's wallet account. The request must contain the `receiver`, which is the recipient's incoming payment `id`, along with the `debitAmount`, which is the exact amount the sender wants to pay.
+
+The `debitAmount` specifies that the sender will pay exactly $100 USD, and the recipient will receive whatever amount remains after currency conversion.
+
+
+
+ ```ts wrap
+ const senderQuote = await client.quote.create(
+ {
+ url: senderWalletAddress.resourceServer,
+ accessToken: senderQuoteGrant.access_token.value
+ },
+ {
+ method: 'ilp',
+ walletAddress: senderWalletAddress.id,
+ receiver: recipientIncomingPayment.id,
+ debitAmount: {
+ value: '10000',
+ assetCode: 'USD',
+ assetScale: 2
+ }
+ }
+ )
+```
+
+
+
+The response returns a `receiveAmount`, a `debitAmount`, and other required information.
+
+- `debitAmount` - The amount the sender must pay (exactly $100.00 USD in our example).
+- `receiveAmount` - The amount the recipient will actually receive ($1,700.00 MXN in our example) after currency conversion.
+
+:::note[Expiring quotes]
+Quotes include an `expiresAt` timestamp. Create the outgoing payment before the quote expires. If creation fails because the quote expired, request a new quote and try again.
+:::
+
+
+Example response
+ The following shows an example response from the sender's wallet provider.
+
+```json wrap
+{
+ "id": "https://cloudninebank.example.com/quotes/{...}", // url identifying the quote
+ "walletAddress": "https://cloudninebank.example.com/sender",
+ "receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment the quote is created for
+ "debitAmount": {
+ "value": "10000", // Sender pays exactly $100.00 USD
+ "assetCode": "USD",
+ "assetScale": 2
+ },
+ "receiveAmount": {
+ "value": "170000", // Recipient receives $1,700.00 MXN after currency conversion
+ "assetCode": "MXN",
+ "assetScale": 2
+ },
+ "method": "ilp",
+ "createdAt": "2025-03-12T23:22:51.50Z",
+ "expiresAt": "2025-03-12T23:24:51.50Z"
+}
+```
+
+
+
+### 6. Request an interactive outgoing payment grant
+
+Use the sender's `authServer` information received in Step 1 to call the [Grant Request API](/apis/auth-server/operations/post-request).
+
+This call obtains an access token that allows your app to request that an outgoing payment resource be created on the sender's wallet account.
+
+:::note
+Outgoing payments require an interactive grant. This type of grant will obtain the sender's before an outgoing payment is made against their wallet account. You can find more information in the [Open Payments flow](/concepts/op-flow/#outgoing-payment) and [identity providers](/identity/idp) pages.
+:::
+
+
+
+ ```ts wrap
+ const pendingSenderOutgoingPaymentGrant = await client.grant.request(
+ {
+ url: senderWalletAddress.authServer
+ },
+ {
+ access_token: {
+ access: [
+ {
+ identifier: senderWalletAddress.id,
+ type: 'outgoing-payment',
+ actions: ['create'],
+ limits: {
+ debitAmount: {
+ assetCode: 'USD',
+ assetScale: 2,
+ value: '10000',
+ }
+ }
+ }
+ ]
+ },
+ interact: {
+ start: ['redirect'],
+ finish: {
+ method: 'redirect',
+ uri: 'https://myapp.example.com/finish/{...}', // where to redirect your user after they've completed the interaction
+ nonce: NONCE
+ }
+ }
+ }
+ )
+ ```
+
+
+
+
+ Example response
+
+```json wrap
+{
+ "interact": {
+ "redirect": "https://auth.cloudninebank.example.com/{...}", // uri to redirect the sender to, to begin interaction
+ "finish": "..." // unique key to secure the callback
+ },
+ "continue": {
+ "access_token": {
+ "value": "..." // access token for continuing the outgoing payment grant request
+ },
+ "uri": "https://auth.cloudninebank.example.com/continue/{...}", // uri for continuing the outgoing payment grant request
+ "wait": 30
+ }
+}
+```
+
+
+
+### 7. Start interaction with the user
+
+
+
+### 8. Finish interaction with the user
+
+
+
+### 9. Request a grant continuation
+
+In our example, we're assuming the IdP your user interacted with has a user interface. When the interaction completes, your user returns to your app. Now your app can make a continuation request for the outgoing payment grant.
+
+:::note
+In a scenario where a user interface isn't available, consider implementing a polling mechanism to check for the completion of the interaction.
+:::
+
+Call the [Grant Continuation Request API](/apis/auth-server/operations/post-continue). This call requests an access token that allows your app to request that an outgoing payment resource be created on the sender's wallet account.
+
+Issue the request to the `continue.uri` provided in the initial outgoing payment grant response (Step 6).
+
+Include the `interact_ref` returned in the redirect URI's query parameters.
+
+
+
+ ```ts wrap
+ const senderOutgoingPaymentGrant = await client.grant.continue(
+ {
+ url: pendingSenderOutgoingPaymentGrant.continue.uri,
+ accessToken: pendingSenderOutgoingPaymentGrant.continue.access_token.value
+ },
+ {
+ interact_ref: interactRef
+ }
+ )
+ ```
+
+
+
+
+ Example response
+
+```json wrap
+{
+ "access_token": {
+ "value": "...", // final access token required before creating outgoing payments
+ "manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token
+ "access": [
+ {
+ "type": "outgoing-payment",
+ "actions": ["create", "read"],
+ "identifier": "https://cloudninebank.example.com/sender",
+ "limits": {
+ "receiver": "https://happylifebank.example.com/incoming-payments/{...}" // url of the incoming payment that's being paid
+ }
+ }
+ ]
+ },
+ "continue": {
+ "access_token": {
+ "value": "..." // access token for continuing the request
+ },
+ "uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri
+ }
+}
+```
+
+
+
+### 10. Request the creation of an outgoing payment resource
+
+Use the access token returned in Step 9 to call the [Create Outgoing Payment API](/apis/resource-server/operations/create-outgoing-payment/). Include the `quoteId` in the request. The `quoteId` is the `id` returned in the Create Quote API response (Step 5).
+
+
+
+ ```ts wrap
+ const senderOutgoingPayment = await client.outgoingPayment.create(
+ {
+ url: senderWalletAddress.resourceServer,
+ accessToken: senderOutgoingPaymentGrant.access_token.value
+ },
+ {
+ walletAddress: senderWalletAddress.id,
+ quoteId: senderQuote.id
+ }
+ )
+```
+
+
+
+
+Example response
+The following shows an example response when an outgoing payment resource is created on the sender's account.
+
+```json wrap
+{
+ "id": "https://cloudninebank.example.com/outgoing-payments/{...}", // url identifying the outgoing payment
+ "walletAddress": "https://cloudninebank.example.com/sender",
+ "receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment being paid
+ "debitAmount": {
+ "value": "10000", // Sender pays exactly $100.00 USD
+ "assetCode": "USD",
+ "assetScale": 2
+ },
+ "receiveAmount": {
+ "value": "170000", // Recipient receives $1,700.00 MXN after currency conversion
+ "assetCode": "MXN",
+ "assetScale": 2
+ },
+ "sentAmount": {
+ "value": "0",
+ "assetCode": "USD",
+ "assetScale": 2
+ },
+ "createdAt": "2022-03-12T23:20:54.52Z"
+}
+```
+
+
+
+:::note[Complete the incoming payment (optional)]
+After your outgoing payment completes, you can call the [Complete Incoming Payment API](/apis/resource-server/operations/complete-incoming-payment/) to mark the recipient's incoming payment as complete. This instructs the recipient's provider to stop accepting additional payments for that incoming payment.
+:::