Accept ACH debits and credits through the PaidYET API.
Embank merchants and partners originate ACH through the PaidYET REST API. One request debits a verified bank account. The same transaction can later be refunded, which sends an ACH credit back to the account holder. A sandbox returns accepted and declined outcomes on demand so your handling can be built before anything is originated in production.
HTTPS only. All paths below are relative to this base.
Same paths, mock processor, separate credentials.
Exchange your merchant API key at POST /login for a short-lived JWT.
7,000 calls per day. Contact Embank if your volume needs more.
Three things you need
ACH is enabled per merchant account. Once Embank has approved the merchant for ACH origination, the integration itself is a small surface: one key, one token exchange, one endpoint.
An ACH-approved merchant account
Embank onboards each merchant and enables ACH on the account. Partners integrating a portfolio of merchants go through the partner program, and each merchant still gets its own account.
A merchant API key
Generated in the PaidYET dashboard under Settings → Developers. Keys are scoped to one merchant. Partners hold one key per merchant and send the matching key with each request. Rotate keys individually.
Sandbox credentials
A separate sandbox account with its own key. Ask Embank and we will arrange it, or email PaidYET support with the subject line “Sandbox Account Request.” Point your integration at the sandbox base URL until you are ready to go live.
Partner-level API keys that acted across many merchants with a merchant UUID have been deprecated by PaidYET. Existing partner keys continue to work, but new integrations should use merchant-level keys.
Requests and responses
| Item | Convention |
|---|---|
| Transport | HTTPS to the production or sandbox base URL. JSON request and response bodies with Content-Type: application/json. |
| Authentication | POST your API key to /login. The response carries a JWT and its expiry as a Unix timestamp. Send it as Authorization: Bearer <token> on every other call, and log in again when it expires. A 400 with “Expired token” or a 401 means the token needs refreshing. |
| Amounts | Decimal dollars, not cents. 125.00 is one hundred twenty-five dollars. Zero or greater. Do not include surcharge in the amount. |
| Payment method | Exactly one of check, credit_card or emv_data per transaction. ACH always uses check. |
| Identifiers | PaidYET assigns the numeric transaction id. You may attach your own external_id, order_id and invoice for reconciliation. The API does not offer an idempotency key, so record the returned id before retrying a request that timed out. |
| Timestamps | YYYY-MM-DD HH:MM:SS in UTC on every date_created and search parameter. |
| Envelope | Every response has messages (notice, success, warning and error arrays, each item with a code and content) and payload (type, items, count, pages, page, per_page). A created transaction is payload.items[0]. |
| Outcomes | HTTP 400 and 401 are authentication problems. Everything else, including declines, comes back as 200 with the result in the transaction’s status, result and error fields. |
{
"key": "YOUR_MERCHANT_API_KEY"
}
// 200 OK
{
"status": "OK",
"result": {
"status": "success",
"token": "eyJ0eXAiOiJKV1QiLCJh...",
"exp": "1761700000",
"message": "Logged in successfully."
}
}
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJh... { "amount": 1850.00, "check": { "accountholder": "Dana Ruiz", "account": "000123456789", "routing": "021000021" }, "email": "[email protected]", "invoice": "MAR-RENT-4B", "external_id": "rent-4b-2026-03", "send_receipt": true } // 200 OK (payload.items[0]) { "id": 1234567890, "amount": 1850.00, "type": "sale", "status": "approved", "result": "OK", "date_created": "2026-03-01 15:04:11" }
| Method | Path | Purpose |
|---|---|---|
| POST | /login | Exchange a merchant API key for a bearer token. |
| POST | /token | Tokenize a bank account for reuse. Returns a token in place of account and routing numbers. |
| POST | /transaction | Create an ACH debit against a bank account or a stored token. |
| GET | /transaction/{id} | Retrieve a transaction, including its current ACH status. |
| GET | /transaction/search | List transactions by date, date range, amount, name, status or type. |
| PUT | /transaction/{id} | Void a debit that has not yet been sent for origination. |
| POST | /transaction/{id} | Refund a funded debit. This originates the ACH credit back to the account holder. |
The same API also processes cards, terminals and hosted checkout. Those endpoints and fields are documented in the PaidYET reference and are omitted here. Field names and limits on this page are taken from that reference; where the two differ, the reference governs.
Collecting from a bank account
An ACH debit is a POST /transaction with an amount and a check object. Supply the account holder’s name with the account and routing numbers, or supply a token from a previous tokenization. A successful request returns status: "approved", which means the debit has been accepted into PaidYET’s system as Processed. It has not yet been originated to the ACH network, and it can still be voided.
The request does not carry an SEC code or an authorization record. Obtaining authorization from the account holder, in the form the Nacha rules require for your entry class, and retaining it for the required period remains your obligation as the originator. See Nacha compliance for what that record must contain.
| Field | Description |
|---|---|
| accountholder | Name of the person or business on the account. Required. |
| account | Bank account number as a string. Required unless a token is supplied. |
| routing | Nine-digit ABA routing number as a string. Required unless a token is supplied. |
| token | Token from POST /token. Replaces account and routing. |
| external_id | Your own identifier for the account. Optional. |
| Field | Description |
|---|---|
| amount | Decimal dollars. Required. |
| external_id | Your reference for the transaction. Searchable from your side; not generated by PaidYET. |
| invoice | Invoice number shown on the receipt and in the dashboard. |
| order_id | Order reference. |
| Account holder’s email. With send_receipt: true, PaidYET emails a receipt. | |
| customer | Customer object with name and contact media. Links the transaction to a stored customer record. |
| billing_address | Street, city, two-letter state and ZIP. |
| comments | Free text stored with the transaction. |
Tokenize once, debit repeatedly
For recurring collection, tokenize the account first with POST /token and store the returned token instead of the account and routing numbers. Check tokens hold only the account and routing numbers, and those numbers cannot be read back after tokenization. Tokens are not tied to a single merchant, so a partner can reuse one across the merchants it manages. Attach an external_id at creation if you want to look the token up by your own reference later.
The schedule and recurring options on the transaction endpoint are documented for card payments only. For recurring ACH, schedule the debits on your side and create each one against the stored token.
{
"accountholder": "Dana Ruiz",
"account": "000123456789",
"routing": "021000021",
"external_id": "unit4b"
}
// 200 OK (payload.items[0])
{
"token": "abcd1234",
"accountholder": "Dana Ruiz",
"external_id": "unit4b"
}
// later: debit the token
{
"amount": 1850.00,
"check": { "token": "abcd1234" },
"external_id": "rent-4b-2026-04"
}
Sending funds back to the account holder
An ACH credit is issued as a refund against a prior debit. Post to the original transaction’s id with type: "refund" and the amount. The credit is originated to the same account the debit was drawn on. For ACH, the refund amount cannot exceed the original debit, and a transaction can only be refunded once it has reached Funded. Until then, a void is the only reversal available, and only while the debit is still Processed.
Credits that are not tied to a prior debit, such as payouts or disbursements, are not available through this endpoint. Contact Embank if your use case needs them.
{
"type": "refund",
"amount": 1850.00
}
// 200 OK: the refund is returned as its own
// transaction with type "refund"
{
"type": "void"
}
// Only while the debit is Processed. Once it
// has been downloaded for origination the
// void is refused; wait for Funded and refund.
| Debit status | Void | Refund | Note |
|---|---|---|---|
| Processed | Yes | No | Nothing has left PaidYET. A void leaves no trace on the account holder’s statement. |
| Downloaded | No | No | In transit to the back office. Wait. |
| Originated | No | No | Presented to the Federal Reserve. Wait for settlement. |
| Funded | No | Yes | Refund up to the original amount. Both the debit and the credit appear on the statement. |
| Any return | No | No | Funds never settled or have already been pulled back. Handle per the return category below. |
What happens after the debit is accepted
The API answers with approved or declined at submission. The ACH status on the transaction then advances as the entry moves through origination, settlement and the return window. Retrieve the transaction to read it.
Status names are PaidYET’s. Back-office exception codes are listed on the B.O. exception codes page. The Nacha return reason codes behind each return category are on the return code reference.
| Status | Meaning | Handling to build |
|---|---|---|
| Processed | Newly accepted and authenticated. | Record the transaction id. Offer cancellation as a void while this status holds. |
| Downloaded | Downloaded to the back-office system. Temporary. | Nothing. Do not attempt a void or refund. |
| BO Exception | Failed back-office processing before origination. | Alert an operator. Correct the account details or authorization and submit a new debit. Codes are on the exception codes page. |
| Originated | Validated and presented to the Federal Reserve. | Treat as pending. Funds are not yet yours. |
| Funded | Paid to the merchant. | Mark the receivable paid. Refunds become available. |
| Refunded | Credited back to the consumer. | Reverse the receivable and keep the refund transaction id with the original. |
| Invalid Return | Returned for invalid account information. | Stop debiting the account. Re-collect and re-verify account details before trying again. |
| NSF Return | Returned for non-sufficient funds. | Notify the account holder. Re-present only within the limits the Nacha rules allow, at most two more times. |
| Chargeback Return | Returned as unauthorized or authorization revoked. | Block further debits to the account. Pull up the authorization record; do not re-present. |
| Returned | Any other return type. | Read the reason and route it by category. See the return code reference. |
| IsLateReturn | Attribute, not a status. The debit was Funded and then returned. | Reverse the payment you already recognized. Watch the rate: late unauthorized returns count toward the Nacha thresholds. |
Reading status and reconciling
ACH settles and returns over days, not seconds, so status is read rather than pushed. Retrieve a single transaction by id, or search a date or date range to sweep everything that changed. A daily sweep of the previous business day’s debits, plus a second sweep at the end of the standard return window, catches funding and returns without polling every record.
Webhooks are available for batch events: when a batch is created, closed to the settlement platform, or fails to close. They are configured in the PaidYET dashboard under Account → Tools → Access Points. A per-transaction event for ACH returns is not published, so build the sweep regardless.
// At least one parameter is required. // date=YYYY-MM-DD or datetime_start + datetime_end // amount, first_name, last_name // status=approved|declined|scheduled // type=sale,refund,void // 200 OK { "payload": { "type": "transaction", "count": 2, "items": [ { "id": 1234567890, "amount": 1850.00, "external_id": "rent-4b-2026-03", ... }, { "id": 1234567891, "amount": 425.00, "external_id": "rent-2a-2026-03", ... } ] } }
{
"event": "batch.closed",
"data": [
{
"id": 22642,
"status": "closed",
"amount": "2275.00",
"merchant": { "name": "Acme Property Mgmt" },
"number_transactions": 2,
"date_opened": "2026-03-01 13:07:39",
"date_closed": "2026-03-02 16:20:54"
}
]
}
// Other events: batch.created, batch.failed
Transaction-level response codes are reported in the transaction’s error field as a code and message. The format is not guaranteed, so match on the code and log the message rather than parsing the string.
Test accounts
The sandbox runs every request through a mock processor and originates nothing. Use the account and routing numbers below to exercise the accepted and declined paths at submission.
| Account number | Routing number | Outcome | Decline code | Handling to build |
|---|---|---|---|---|
| 111111 | 999999992 | Accepted | — | Normal path. Store the id and show the debit as pending. |
| 100 | 999999992 | Declined | 100 | Decline at submission. Surface the message and let the user correct and resubmit. |
| 200 | 999999992 | Declined | 200 | As above, with a different code, to prove you branch on the code. |
| 300 | 999999992 | Declined | 300 | As above. |
| Any other | Any | Error | — | Unrecognized test account. Treat as a validation failure. |
The sandbox does not advance a debit through Originated, Funded or any return status, and it does not send real receipts. Build and unit-test the lifecycle handling above against the status table, then confirm it against the first live transactions with Embank’s support team. Sandbox and production keys are not interchangeable.
Request credentials
Tell us what you are integrating and we will arrange sandbox access, confirm the merchant’s ACH approval, and put you in contact with the team supporting the API.