Corgi BankDocumentation
OpenAPI

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

RailWhen the money movesHolds and postingComing back
Book transferIn 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.
ACHThe 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.
WireIn 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.
InstantIn 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.

POST /transfers/book
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.

POST /transfers/ach
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.

POST /transfers/wire
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.

POST /transfers/realtime
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.created when the entry is accepted. Its later states (batched, sent, settled, returned) publish no event of their own: read the transfer, or follow its file through payments.ach_file.sent and payments.ach_file.settled.
  • Wire: payments.wire.settled is the debit. payments.wire.failed and payments.wire.rejected release the funds. payments.wire.queued and payments.wire.held_for_review mean it has not left. See States and notifications.
  • Instant: payments.instant.accepted is the debit and payments.instant.rejected_by_rail releases the funds. payments.instant.sent means the rail has not answered yet.

Handle a return

  • ACH: the transfer becomes returned with return_code and return_reason, and the posting is reversed. No event announces it, so poll GET /transfers?type=ach&status=returned. See ACH returns.
  • Wire: payments.wire.returned arrives on the original with return_reason_code. The return is an inbound wire with kind return and original_wire_id, credited to the account that paid.
  • Instant: payments.instant.returned arrives on the original. The return is an inbound Realtime Transfer with kind return and original_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.

to move to open esc to close