Skip to navigation

ACH and Plaid funding

View as Markdown

NinjaTrader Connect supports ACH deposits and withdrawals from a checking or savings account linked through Plaid. The flow creates or repairs an ACH application for a NinjaTrader trading account, then uses the approved application for funding.

These endpoints are Live-only. Do not send createPlaidLinkToken, linkPlaidItem, cancelAchApplication, or submitFundTransaction to the Demo base URL.

For testing, use a bearer access token for the NinjaTrader staging Live host, which is backed by Plaid Sandbox.

Prerequisites

Your integration needs:

  • a bearer access token for the user
  • the user’s eligible live trading account id and exact name
  • Plaid Link in the user’s client
  • a registered redirectUri for a browser or iOS OAuth return flow, or a registered androidPackageName for an Android app-link flow
  • the USD currency ID resolved for the target environment

Store durable workflow state in your own backend. A page reload, tab close, app restart, or OAuth redirect must not lose the selected mode, target, or reconciliation state.

Complete the funding disclosure

Before an ACH deposit, the user must review and sign the Payment Processor Disclosure and Authorization. Follow Funding disclosures, and confirm that the signature was stored before you enable the deposit action.

The disclosure applies to ACHR deposits. It does not gate ACHS withdrawals. NinjaTrader remains the source of truth and rejects a deposit with AchDisclosureNotAccepted when the disclosure is missing.

Your integration must complete the disclosure signing flow and confirm it before you submit a deposit.

Load the accounts and ACH applications

Before you create a Plaid Link token, load both prerequisite lists:

  1. Call accountList. Hide accounts whose legalStatus is IRA.
  2. Call achApplicationList. Match applications to trading accounts by dormanAccountId.

Keep each eligible account’s numeric id and exact name. You send them later as dormanAccountId and dormanAccountName; a name that doesn’t match the account record causes linkPlaidItem to fail.

An account can have only one active ACH application. An application is active when it isn’t archived and its status is neither Rejected nor RequiresRelink. Don’t start a new link for an account that already has an active application.

New-link and relink modes are mutually exclusive:

  • New link: select one eligible trading account. Don’t send achApplicationId when you create the link token.
  • Relink: select one unarchived application whose status is RequiresRelink, and send its id as achApplicationId. The application determines the NinjaTrader trading account. Derive dormanAccountId from the application and use that account’s current exact name from accountList; don’t accept or use a separate account selection.

Changing the mode or target invalidates an existing Plaid Link token. Discard the token and create a new one before opening Plaid Link.

For a new link, call createPlaidLinkToken with an empty JSON object:

{}

For a relink, include the selected RequiresRelink application’s ID:

{
"achApplicationId": 12345
}

The platform fields are optional and mutually exclusive:

  • Send redirectUri for a browser or iOS OAuth return flow.
  • Send androidPackageName for an Android app-link flow.
  • Send neither when the client flow doesn’t use one.
  • Never send both.

Register the value in NinjaTrader’s Plaid configuration before using it, and omit blank optional fields. Changing either platform field invalidates an existing token.

Prevent concurrent token requests. While one request is in flight, keep the create action unavailable. If the request times out, clear the in-flight guard and require an explicit manual retry; don’t start a second request automatically.

Inspect errorText even when the response is HTTP 200. Continue only when it is absent, null, or empty and the response contains a non-empty token. Never log, place in a URL, or display the complete token. Keep it only for the life of the link flow, in protected state that can survive an OAuth return when the client needs one.

OAuth institutions require a return flow that reopens Plaid Link with receivedRedirectUri. This applies only to native mobile apps on iOS and Android. The flow requires durable state for the token. Implement and test the return flow before enabling OAuth institutions.

Open Plaid Link with the token. If the user exits, show Plaid’s exit error and metadata, and don’t call NinjaTrader.

After Plaid succeeds, select the first returned account that:

  • Has type depository
  • Has subtype checking or savings
  • Doesn’t have a verification_status

If no returned account matches, stop the flow instead of sending an incomplete request.

Map Plaid’s onSuccess(publicToken, metadata) result into linkPlaidItem:

{
"financialInstitutionName": "Example Bank",
"accountType": "Checking",
"accountNumber": "1234",
"routingNumber": "ins_123456",
"dormanAccountId": 123456,
"dormanAccountName": "LIVE123456",
"accountId": "plaid-account-id",
"publicToken": "public-sandbox-example"
}

Use Checking for a Plaid checking subtype and Savings for savings. Use the account mask for accountNumber, or "****" when no mask is returned. Use the Plaid institution ID for routingNumber, or an empty string when none is returned. NinjaTrader replaces these placeholder account and routing values with the bank details it retrieves from Plaid.

Inspect errorText even when the response is HTTP 200. Continue only when it is absent, null, or empty and the response contains a numeric achApplicationId.

After linkPlaidItem succeeds, call achApplicationList once and find the returned ID:

  • Approved means ACH funding can proceed.
  • UnderReview is a valid link result, but funding must wait. Use a limited number of later checks with increasing delays; don’t poll indefinitely.
  • If the read fails or doesn’t contain the returned ID, report the link as submitted with confirmation unavailable. Don’t call linkPlaidItem again. Reconcile with a later read or contact NinjaTrader support.

Submit an ACH funding request

Call submitFundTransaction only when the selected account has an unarchived Approved ACH application.

Resolve fundCurrencyId with currencyList. Currency IDs differ by environment. The examples below use the staging USD ID 1; don’t treat that value as an application invariant.

For a deposit, use ACHR and outgoing: false:

{
"accountId": 123456,
"outgoing": false,
"fundPaymentType": "ACHR",
"fundAmount": 250,
"fundCurrencyId": 1
}

For a withdrawal, use ACHS and outgoing: true:

{
"accountId": 123456,
"outgoing": true,
"fundPaymentType": "ACHS",
"fundAmount": 250,
"fundCurrencyId": 1
}

Send a positive amount with no more than two decimal places, except for a full-balance withdrawal. Don’t add fee, bank-account, or description fields.

Withdraw the full balance

Send an ACHS withdrawal with fundAmount: 0 and fullBalance: true:

{
"accountId": 123456,
"outgoing": true,
"fundPaymentType": "ACHS",
"fundAmount": 0,
"fundCurrencyId": 1,
"fullBalance": true
}

NinjaTrader determines the amount during processing. The request locks the trading account for immediate liquidation until it is approved or rejected. Open positions, working orders, ACH holds, pending deposits, or another pending full-balance request can cause rejection. Require an explicit confirmation that explains this effect before sending the request.

Guard against duplicates

Keep the deposit or withdrawal action unavailable while a request is in flight. After the request might have reached NinjaTrader, don’t send another one until you reconcile the transaction and account balance.

Handle the funding response

Most business failures arrive in an HTTP 200 response. Always inspect errorText, errorHeader, and errorCode before treating the operation as successful. A successful response must also contain a valid fundTransaction object. Display unknown status and error-code values as returned instead of assuming a fixed enumeration.

  • Known business error: correct the reported condition before allowing another request. For example, AchNoApprovedApplications means the selected account doesn’t have an approved ACH application.
  • Network error, HTTP 5xx, non-JSON body, malformed response, or errorCode: Unknown: the transaction may have been created. Keep the flow locked against duplicates, and check the transaction and account balance before sending another request.
  • Valid fundTransaction: show the returned ID and status. Pending, InTransit, and Locked are processing states, not failures.

Never retry a submitted funding request automatically.

Cancel an ACH application

Call cancelAchApplication with the application’s ID from achApplicationList:

{
"achApplicationId": 12345
}

Cancellation archives the application and removes its stored Plaid connection. The API has no operation to undo it, so require an explicit confirmation before sending the request.

Business failures arrive in an HTTP 200 response with ok: false and errorText. Treat the mutation as accepted only when the response is { "ok": true }.

After an accepted cancellation, call achApplicationList once and find the target application:

  • Confirm cancellation only when the application has archived: true.
  • If the read fails, the application is missing, or it still has archived: false, report the cancellation as submitted with confirmation unavailable. Don’t submit another cancellation automatically.

To use ACH funding again, start a new link after cancellation is confirmed.

Next steps

  • Keep the ACH application and fund-transaction status visible so the user can distinguish a linked bank from a fundable bank.
  • Redact bearer tokens, Plaid link tokens, and Plaid public tokens from logs and user-visible errors.
  • Reconcile uncertain mutations before permitting another submission.
  • If you prefer a NinjaTrader-hosted flow, see Embedding the Funding Page with an iframe.