Payments Quickstart

Learn how to send an ACH payment to a new counterparty and monitor the status of the payment.

TAB-API

1. Retrieve your API Key

Once you have access to Modern Treasury, log in and go to your API Keys page. There you will find your Organization ID and API keys.

Authentication with the Modern Treasury API is done by using HTTP Basic authentication, with your Organization ID as the username and the API Key as the password. When using curl, you can use the -u option to pass these values directly.

If using one of our server side SDKs, you can either pass these values in directly, or the library will pull from defined environment variables.

curl --request GET \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/ping
from modern_treasury import ModernTreasury

modern_treasury = ModernTreasury(
  # defaults to os.environ.get("MODERN_TREASURY_API_KEY")
  api_key="your-api-key",
  organization_id="your-organization-id",
)

pong = modern_treasury.ping()
print(pong)
import ModernTreasury from 'modern-treasury';

const modernTreasury = new ModernTreasury({
  apiKey: 'your-api-key', // defaults to process.env["MODERN_TREASURY_API_KEY"]
  organizationId: 'your-organization-id',
});

async function main() {
  const pong = await modernTreasury.ping()
  console.log(pong);
}

main().catch(console.error);

2. Retrieve your Internal Account ID

When you create a payment order, you must specify which account you want the transaction to originate from. If you are sending someone money, this is the account that the money will be taken out of. If you are charging someone, this is the account that the money will be deposited into.

To get an internal account ID, you can pick one from your internal accounts page, which is here. Any of the IDs will work for testing, although you can refer to Internal Accounts to see the differences in how the test banks process payments.

Or, if you want to hit our API to get all the internal accounts, issue the following API request. You can see the documentation for internal accounts here.

In this example, we are planning to send a payment with the ACH payment type, so let us query for internal accounts that have the capability to send this type of payment.

curl --request GET \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/internal_accounts?payment_type=ach
from modern_treasury import ModernTreasury

modern_treasury = ModernTreasury(
  # defaults to os.environ.get("MODERN_TREASURY_API_KEY")
  api_key="your-api-key",
  organization_id="your-organization-id",
)

internal_accounts = modern_treasury.internal_accounts.list({
  "payment_type": "ach"
})

print(internal_accounts.items[0].id)
import ModernTreasury from 'modern-treasury';

const modernTreasury = new ModernTreasury({
  apiKey: 'your-api-key', // defaults to process.env["MODERN_TREASURY_API_KEY"]
  organizationId: 'your-organization-id',
});

async function main() {
  const internalAccounts = await modernTreasury.internalAccounts.list({
    payment_type: 'ach'  
  });
  console.log(internalAccounts.items[0].id);
}

main().catch(console.error);

You should have received an array of all your internal accounts that can send payment orders with the payment type "ach". For more information on how we've set up your accounts, see the Sandbox section.

For this quickstart, we'll just use one of the account IDs to originate the payment order from. In this response, we'll use the first one, 0f8e3719-3dfd-4613-9bbf-c0333781b59f.

[
  {
    "id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
    "object": "internal_account",
    ...
  },
  ...
]
[
  {
    "id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
    "object": "internal_account",
    "account_type": null,
    "party_name": "Modern Treasury",
    "party_type": null,
    "party_address": null,
    "account_details": [
      {
        "id": "aaf74f7e-d697-4a73-95a3-05bede2edce6",
        "object": "account_details",
        "account_number": "23174905882992971",
        "account_number_type": null,
        "created_at": "2019-11-09T00:11:07Z",
        "updated_at": "2019-11-09T00:11:07Z"
      }
    ],
    "routing_details": [
      {
        "id": "a3649136-f8d2-46e8-8b41-327bd7da3110",
        "object": "routing_details",
        "payment_type": null,
        "routing_number": "021000021",
        "routing_number_type": "aba",
        "created_at": "2019-11-09T00:11:07Z",
        "updated_at": "2019-11-09T00:11:07Z"
      }
    ],
    "connection": {
      "id": "05487db0-e234-4ae8-914a-c627f76c987f",
      "object": "connection",
      "vendor_id": "example1",
      "vendor_name": "Gringotts Wizarding Bank",
      "created_at": "2019-11-09T00:11:07Z",
      "updated_at": "2019-11-09T00:11:07Z"
    },
    "created_at": "2019-11-09T00:11:07Z",
    "updated_at": "2019-11-09T00:11:07Z"
  }
]

3. Create a Counterparty

Let's onboard your first counterparty. For the purpose of this example, we can assume you are trying to send them money over ACH.

For more examples, see the section on Counterparty Example Requests.

curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/counterparties \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Kenner, Bach and Ledeen",
    "accounts": [
      {
        "account_type": "checking",
        "routing_details": [
          {
            "routing_number_type": "aba",
            "routing_number": "121141822"
          }
        ],
        "account_details": [
          {
            "account_number": "123456789"
          }
        ]
      }
    ]
  }'
from modern_treasury import ModernTreasury

modern_treasury = ModernTreasury(
  # defaults to os.environ.get("MODERN_TREASURY_API_KEY")
  api_key="my-api-key",
  organization_id="my-organization-id",
)

counterparty = modern_treasury.counterparties.create({
  "name": "Kenner, Bach and Ledeen",
  "accounts": [
    {
      "account_type": "checking",
      "routing_details": [
        {
          "routing_number_type": "aba",
          "routing_number": "121141822"
        }
      ],
      "account_details": [
        {
          "account_number": "123456789"
        }
      ]
    }
  ]
})
import ModernTreasury from 'modern-treasury';

const modernTreasury = new ModernTreasury({
  apiKey: 'your-api-key', // defaults to process.env["MODERN_TREASURY_API_KEY"]
  organizationId: 'your-organization-id',
});

async function main() {
  const counterparty = await modernTreasury.counterparties.create({
    "name": "Kenner, Bach and Ledeen",
    "accounts": [
      {
        "account_type": "checking",
        "routing_details": [
          {
            "routing_number_type": "aba",
            "routing_number": "121141822"
          }
        ],
        "account_details": [
          {
            "account_number": "123456789"
          }
        ]
      }
    ]
  });
  
  console.log(counterparty.accounts[0].id);
}

main().catch(console.error);

Note that the response returns a new Counterparty object with 1 attached external account. You will need the ID of the external account (5acec2ef-987b-4260-aa97-b719eeb0a8d5) for the next step.

{
  "id":"37ba4454-dd33-4aa0-8906-0e2e4103e45c",
  "object": "counterparty",
  ...
}
{
  "id":"37ba4454-dd33-4aa0-8906-0e2e4103e45c",
  "object": "counterparty",
  "name": "Kenner, Bach and Ledeen",
  "email": null,
  "send_remittance_advice": false,
  "metadata": {},
  "accounts": [
    {
      "id":"5acec2ef-987b-4260-aa97-b719eeb0a8d5",
      "object": "external_account",
      "account_type":"checking",
      "party_name": "Kenner, Bach and Ledeen",
      "party_type": null,
      "party_address": null,
      "account_details": [
        {
          "id":" 81a7cd32-39f5-4f0c-873f-4d9137ec9cd9",
          "object": "account_detail",
          "account_number_safe": "6789",
          "account_number_type": "other",
          "live_mode": true,
          "created_at": "2019-11-09T00:11:07Z",
          "updated_at": "2019-11-09T00:11:07Z"
        }
      ],
      "routing_details": [
        {
          "id":"5ceb251f-0235-48a2-81cb-0c668f5ee81b",
          "object": "routing_detail",
          "payment_type": null,
          "routing_number": "121141822",
          "routing_number_type": "aba",
          "bank_name": "BANK OF AMERICA CALIFORNIA, NA",
          "bank_address": {
            "id": "2f1e12dd-de80-44aa-92cd-f0e4101b8e54",
            "object": "address",
            "line1": "PO BOX 27025",
            "line2": null,
            "locality": "RICHMOND",
            "region": "VA",
            "postal_code": "23261-7025",
            "country": "US",
            "live_mode": true,
            "created_at": "2019-11-09T00:11:07Z",
            "updated_at": "2019-11-09T00:11:07Z"
          },
          "live_mode": true,      
          "created_at": "2019-11-09T00:11:07Z",
          "updated_at": "2019-11-09T00:11:07Z"
        }
      ],
      "created_at": "2019-11-09T00:11:07Z",
      "updated_at": "2019-11-09T00:11:07Z"
    }
  ],
  "live_mode": true,
  "created_at": "2019-11-09T00:11:07Z",
  "updated_at": "2019-11-09T00:11:07Z"
}

4. Create a Payment Order

Now we can create a payment order to send the counterparty $10 over ACH. As we mentioned, you will use your internal account ID as the originating_account_id and the counterparty's external account ID as the receiving_account_id.

You can see additional sample requests for payment orders here.

curl --request POST \
  -u ORGANIZATION_ID:API_KEY \
  --url https://app.moderntreasury.com/api/payment_orders \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "ach",
    "amount": 1000,
    "direction": "credit",
    "currency": "USD",
    "originating_account_id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
    "receiving_account_id": "5acec2ef-987b-4260-aa97-b719eeb0a8d5"
  }'
from modern_treasury import ModernTreasury

modern_treasury = ModernTreasury(
  # defaults to os.environ.get("MODERN_TREASURY_API_KEY")
  api_key="your-api-key",
  organization_id="your-organization-id",
)

payment_order = modern_treasury.payment_orders.create({
  "type": "ach",
  "amount": 1000,
  "direction": "credit",
  "currency": "USD",
  "originating_account_id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
  "receiving_account_id": "5acec2ef-987b-4260-aa97-b719eeb0a8d5"
})
import ModernTreasury from 'modern-treasury';

const modernTreasury = new ModernTreasury({
  apiKey: 'your-api-key', // defaults to process.env["MODERN_TREASURY_API_KEY"]
  organizationId: 'your-organization-id',
});

async function main() {
  const paymentOrder = await modernTreasury.paymentOrders.create({
    "type": "ach",
    "amount": 1000,
    "direction": "credit",
    "currency": "USD",
    "originating_account_id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
    "receiving_account_id": "5acec2ef-987b-4260-aa97-b719eeb0a8d5"
  })
  console.log(paymentOrder);
}

main().catch(console.error);

This is the response you will see. It contains the ID of the payment order, c5f4009c-bdd6-4cc1-84b2-17974ac9e77a, as well as additional details about it.

{
  "id": "c5f4009c-bdd6-4cc1-84b2-17974ac9e77a",
  "object": "payment_order",
  ...
}
{
  "id": "c5f4009c-bdd6-4cc1-84b2-17974ac9e77a",
  "object": "payment_order",
  "type": "ach",
  "amount": 1000,
  "direction": "credit",
  "originating_account_id": "0f8e3719-3dfd-4613-9bbf-c0333781b59f",
  "receiving_account": {
    "id": "5acec2ef-987b-4260-aa97-b719eeb0a8d5",
    "object": "external_account",
    "account_type": "checking",
    "party_name": "Kenner, Bach & Ledeen",
    "party_type": null,
    "party_address": null,
    "account_details": [
      {
        "id": "81a7cd32-39f5-4f0c-873f-4d9137ec9cd9",
        "object": "account_detail",
        "account_number_safe": "6789",
        "account_number_type": "other",
        "created_at": "2019-11-09T00:11:07Z",
        "updated_at": "2019-11-09T00:11:07Z"
      }
    ],
    "routing_details": [
      {
        "id": "5ceb251f-0235-48a2-81cb-0c668f5ee81b",
        "object": "routing_detail",
        "payment_type": null,
        "routing_number": "121141822",
        "routing_number_type": "aba",
        "created_at": "2019-11-09T00:11:07Z",
        "updated_at": "2019-11-09T00:11:07Z"
      }
    ],
    "created_at": "2019-11-09T00:11:07Z",
    "updated_at": "2019-11-09T00:11:07Z"
  },
  "receiving_account_id": "5acec2ef-987b-4260-aa97-b719eeb0a8d5",
  "receiving_account_type": "external_account",
  "accounting_category_id": null,
  "currency": "USD",
  "effective_date": "2018-11-19",
  "priority": "normal",
  "description": null,
  "statement_descriptor": null,
  "remittance_information": null,
  "metadata": {},
  "status": "approved",
  "counterparty_id": "37ba4454-dd33-4aa0-8906-0e2e4103e45c",
  "transaction_ids": [],
  "charge_bearer": null,
  "foreign_exchange_indicator": null,
  "foreign_exchange_contract": null,
  "nsf_protected": false,  
  "created_at": "2019-11-09T00:11:07Z",
  "updated_at": "2019-11-09T00:11:07Z"
}

5. Monitor payment status

To stay updated about your account, you may either poll our API via requests or receive webhooks. You may configure a URL to use for webhook testing on your webhooks page. We recommend using a tool such as ngrok for this.

Payment Orders initially have a status of approved. As a payment order progresses through its lifecycle, you will receive updates on its status. See the section on payment order webhooks for more details.

Expected Payments initially have a status of unreconciled. When an expected payment is automatically completed in the sandbox, it will become reconciled. If you archive an expected payment, it will become archived.

TAB-Dashboard

1. Log in to your Modern Treasury dashboard

Login by going to https://app.moderntreasury.com.

2. Create a counterparty

A Counterparty is an entity outside of your organization to send or receive money from. They can be an individual or a business. Counterparties can have one or more bank accounts.

Create a new counterparty by following these steps:

  1. Click on "Payments" in the left navigation bar
  2. Click on the "Create New" dropdown
  3. Select "Counterparty"
  4. Enter the "Counterparty Name" of Kenner, Bach and Ledeen
  5. Under "Bank Accounts," select US Account
  6. Enter 121141822 in the "ABA Routing Number" field
  7. Enter 123456789 in the "Account Number" field
  8. Click "Create"

This will result in a new counterparty created with a payable external account. In this example, we used test routing and account numbers. You can also add more information to the counterparty and account to improve reporting and export data.

2. Click to pay

Once you have successfully completed the previous step, you will land in the newly-created counterparty details page. From here you can select the "Pay or charge" option from the "Actions" dropdown button.

3. Create a payment order

Payment orders are instructions sent to the bank for each individual payment. In this step, we will create the basic instructions to send a credit ACH to the previously-created counterparty. Follow the instructions below to create the payment:

  1. Select Pay in the "Action" select
  2. Select the appropriate "From" account, which will be debited to send to the counterparty
  3. In "To", search for and select Kenner, Bach and Ledeen
  4. In "Amount", type 10.00 for $10.00 (default currency is USD)
  5. Click "Create Payment Order"

4. View the payment order

When submitted, you will see the payment order details page. This page shows you everything related to the payment, including the timeline and status.

5. Monitor the payment order

You can view the status of any payment order on its details page. The timeline will show you any state transitions or audit logs necessary to determine the current and previous state of the payment order.