Move money
Money leaves an account four ways: a book transfer inside the bank, ACH, a wire, or an instant payment. This page compares them, shows one request for each, and says how to follow the result and handle a return.
Choose a rail
| Rail | When the money moves | Holds and posting | Coming back |
|---|---|---|---|
| Book transfer | In the request, at any hour. One journal debits one account and credits the other, and the transfer is completed. | No hold. The debit posts in the request. | The API has no return or cancel for it. |
| ACH | The entry joins a NACHA file and settles on its effective date: the next banking day by default, or today with same_day (submit by 10:30 a.m., 2:45 p.m. or 4:45 p.m. ET; $1,000,000 per entry). | A credit places a pending_debit hold. The debit posts when the operator settles the file. | The receiving bank can return a settled entry, and the posting is reversed. |
| Wire | In the request while Fedwire takes customer transfers: 9:00 p.m. ET the evening before a banking day until 6:00 p.m. ET. Outside that the wire is queued for the next opening. | A pending_debit hold. The debit posts when the service answers ACSC. | Only if the receiver returns it. The bank can ask with a return request. |
| Instant | In the request, at any hour, over FedNow or RTP. Up to $10,000,000 per payment. | A pending_debit hold. The debit posts on the rail's pacs.002. | Irrevocable. The bank can ask with a return request, and the receiver may refuse. |
Save a counterparty
A counterparty is a payee a customer saves to pay again. POST /counterparties needs customer_id and name, and the bank infers pay_by from the rest. A routing number and account number mean ach. The bank's own routing number resolves the account and means book. A BIC means international_wire, and an address with no account means check. Set pay_by yourself for realtime or wire; a wire also needs bank_name.
The rail endpoints below take the payee's details inline. To pay a saved counterparty by id, create a scheduled transfer with counterparty_id: without a start_date it runs today, in the request, over the counterparty's rail. Under an approval policy it waits as pending_approval for its approvers.
Send
Every send requires an Idempotency-Key and carries its amount as an integer of minor units beside a currency_code. A customer's token needs a step-up within the last five minutes for a book transfer, a wire or an instant payment. The account's approval policy (payments.approval_required) and the bank's payment limits (payments.limit_exceeded) stand in front of all four. The three rails that leave the bank also run sanctions screening and the outbound fraud rules, which store a payment as held_for_review instead of sending it. A first payment to new account details is one thing those rules look at.
Book transfer
POST /transfers/book names the receiving account by to_account_id or to_account_number. Both accounts must be in the transfer's currency. See Book transfers.
curl -X POST 'https://api.dev.bank.corgi.com/transfers/book' \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"from_account_id": "acct_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
"to_account_number": "4123456784", "amount": 10000,
"currency_code": "USD", "description": "Refund"}'ACH
POST /transfers/ach takes a direction (credit pushes, debit pulls), a sec_code and a description of at most 10 characters. See ACH overview.
curl -X POST 'https://api.dev.bank.corgi.com/transfers/ach' \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"account_id": "acct_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
"direction": "credit", "sec_code": "CCD", "amount": 125000,
"currency_code": "USD", "description": "VENDOR PAY",
"counterparty": {"routing_number": "021000021",
"account_number": "9876543210", "name": "BOLT FASTENERS INC",
"account_type": "checking"}}'Wire
POST /transfers/wire names the receiving bank by aba and bank_name. See Wire overview.
curl -X POST 'https://api.dev.bank.corgi.com/transfers/wire' \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"account_id": "acct_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
"amount": 250000, "currency_code": "USD", "memo": "INVOICE 1002",
"counterparty": {"aba": "021000021", "bank_name": "EXAMPLE BANK NA",
"account_number": "9876543210", "name": "BOLT FASTENERS INC"}}'Instant payment
POST /transfers/realtime names the rail, fednow or rtp. See Instant payments overview.
curl -X POST 'https://api.dev.bank.corgi.com/transfers/realtime' \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"rail": "fednow", "account_id": "acct_2tVh8nqLxq4GbDe0K1F6S9zRcWm",
"amount": 120000, "currency_code": "USD", "remittance": "Rent",
"counterparty": {"routing_number": "021000021",
"account_number": "5551234", "name": "JANE DOE",
"account_type": "checking"}}'Track the result
Subscribe a webhook endpoint to payments.* or to the types below, and read status from the event's data. GET /transfers lists all four kinds together, newest first, filtered by type, account_id and status.
- Book transfer:
payments.book_transfer.completed. - ACH:
payments.ach_transfer.createdwhen the entry is accepted. Its later states (batched,sent,settled,returned) publish no event of their own: read the transfer, or follow its file throughpayments.ach_file.sentandpayments.ach_file.settled. - Wire:
payments.wire.settledis the debit.payments.wire.failedandpayments.wire.rejectedrelease the funds.payments.wire.queuedandpayments.wire.held_for_reviewmean it has not left. See States and notifications. - Instant:
payments.instant.acceptedis the debit andpayments.instant.rejected_by_railreleases the funds.payments.instant.sentmeans the rail has not answered yet.
Handle a return
- ACH: the transfer becomes
returnedwithreturn_codeandreturn_reason, and the posting is reversed. No event announces it, so pollGET /transfers?type=ach&status=returned. See ACH returns. - Wire:
payments.wire.returnedarrives on the original withreturn_reason_code. The return is an inbound wire withkindreturnandoriginal_wire_id, credited to the account that paid. - Instant:
payments.instant.returnedarrives on the original. The return is an inbound Realtime Transfer withkindreturnandoriginal_id.
To get back a wire or an instant payment the account sent, an operator sends a return request. To send back money the account received, an operator returns it. Returns and return requests and Incoming payments and returns cover both. In the sandbox, POST /simulate/transfers/ach/return, POST /simulate/transfers/wire/return and POST /simulate/transfers/realtime/return play the other bank sending the money back.