> This page is for NT Connect.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://partner.ninjatrader.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://partner.ninjatrader.com/_mcp/server.

# Link Plaid Item

POST https://live.tradovateapi.com/v1/achApplication/linkplaiditem
Content-Type: application/json

### Link a Plaid bank account to a NinjaTrader account.**Available to:** All authenticated users**Environments:** Live**[Rate Limit](/connect/overview/core-concepts/rate-limits):** No endpoint-specific limitSubmit the public token and selected depository account returned by a successful Plaid Link session. The response returns the ACH application's ID in `achApplicationId`.Set `dormanAccountId` and `dormanAccountName` to the selected NinjaTrader account's ID and exact name. Set `accountId` to Plaid's account ID and `publicToken` to the public token from the same Plaid Link session. Map the remaining client-visible Plaid values as follows:- Set `financialInstitutionName` to the institution name, or an empty string when Plaid doesn't provide one.
- Set `accountNumber` to the account mask, or `"****"` when Plaid doesn't provide one. This field must be non-empty.
- Set `routingNumber` to the Plaid institution ID, or an empty string when Plaid doesn't provide one.The service replaces the account and routing numbers with the verified values returned by Plaid before storing the application.For a new connection, the application starts as `UnderReview`. It changes to `Approved` during the request when the verified bank-account owner matches the NinjaTrader account holder; otherwise, it remains `UnderReview` for manual review. An `UnderReview` application is valid but can't be used for funding until it becomes `Approved`. Refresh the user's ACH applications once after this request, and use bounded later checks when the application needs review\.For a relink, first create the Plaid Link token with the existing `RequiresRelink` application's ID. After the user reconnects, this request updates the existing application and returns it to `Approved`. For a tokenized account, a relink attempt fails if the selected bank account doesn't match the original persistent bank account.**Warning:** Business failures return `HTTP 200` with `errorText` and no `achApplicationId`. Check `errorText` before treating the link as complete.**Common Failure Scenarios**- The NinjaTrader account ID and name don't match.
- Another active ACH application already exists for the NinjaTrader account.
- Plaid doesn't return the selected account, ACH account numbers, or identity information.
- The institution is blocked, the routing number isn't for a United States bank, or the bank account is already linked to the profile.
- The selected NinjaTrader account is an IRA.
- A tokenized-account relink uses a different persistent bank account.**Error Messages**| Message                                                                                             | Response             | Trigger                                                                                      |
| --------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `"Only one active ACH application is allowed."`                                                     | 200 (in `errorText`) | An active application already exists for the NinjaTrader account.                            |
| `"Wrong parameters"`                                                                                | 200 (in `errorText`) | `dormanAccountName` doesn't match the account identified by `dormanAccountId`.               |
| `"IRA Accounts are not allowed to setup ACH."`                                                      | 200 (in `errorText`) | `dormanAccountId` identifies an IRA.                                                         |
| `"Institution details must be provided for Instant Auth."`                                          | 200 (in `errorText`) | Plaid doesn't provide an institution name and `financialInstitutionName` is absent.          |
| `"Unable to find Plaid account associated with this item."`                                         | 200 (in `errorText`) | `accountId` isn't present in Plaid's account data for the exchanged token.                   |
| `"No ACH numbers found for this account."`                                                          | 200 (in `errorText`) | Plaid doesn't return ACH account and routing numbers for `accountId`.                        |
| `"Unable to retrieve identity information for this account."`                                       | 200 (in `errorText`) | Plaid doesn't return account-owner identity data.                                            |
| `"Institution can't be linked via compliance regulation, please try with a different institution."` | 200 (in `errorText`) | The selected institution is blocked.                                                         |
| `"Only USA banks are allowed to link via ACH."`                                                     | 200 (in `errorText`) | Plaid returns an international routing number.                                               |
| `"This bank account is already linked to this profile."`                                            | 200 (in `errorText`) | The same persistent bank account is already active for this profile and NinjaTrader account. |
| `"Your bank connection needs to be restored. Please relink your account using the same bank."`      | 200 (in `errorText`) | A tokenized-account relink selects a different persistent bank account.                      |
| `"Something went wrong, try again. If it keeps happening, choose another funding method."`          | 200 (in `errorText`) | An unexpected error occurs while linking.                                                    |

Reference: https://partner.ninjatrader.com/connect/api/rest-api-endpoints/users/link-plaid-item

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Request

### Body (application/json)

This endpoint expects a LinkPlaidItem.

- `accountType` (enum, required) — BrokerageCash, CD, Checking, Educational, EmployeeRetirementAccount401k, HealthSavingsAccount, Individual, IndividualRetirementAccount, JointTenantsWithRightsOfSurvivorship, MoneyMarket, Other, RothIRA, Savings
  - Allowed values: `BrokerageCash`, `CD`, `Checking`, `Educational`, `EmployeeRetirementAccount401k`, `HealthSavingsAccount`, `Individual`, `IndividualRetirementAccount`, `JointTenantsWithRightsOfSurvivorship`, `MoneyMarket`, `Other`, `RothIRA`, `Savings`
- `accountNumber` (string, required)
- `dormanAccountId` (long, required) — Dorman Account
- `dormanAccountName` (string, required)
- `accountId` (string, required)
- `publicToken` (string, required)
- `financialInstitutionName` (string, optional)
- `routingNumber` (string, optional)

## Response

### 200

AchApplicationResponse

- `errorText` (string, optional) — Non-empty if the request failed
- `achApplicationId` (long, optional)

## Examples

**Request**

```json
{
  "accountType": "BrokerageCash",
  "accountNumber": "string",
  "dormanAccountId": 1,
  "dormanAccountName": "string",
  "accountId": "string",
  "publicToken": "string"
}
```

**Response**

```json
{
  "errorText": "string",
  "achApplicationId": 1
}
```

**SDK Code**

```python
import requests

url = "https://live.tradovateapi.com/v1/achApplication/linkplaiditem"

payload = {
    "accountType": "BrokerageCash",
    "accountNumber": "string",
    "dormanAccountId": 1,
    "dormanAccountName": "string",
    "accountId": "string",
    "publicToken": "string"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://live.tradovateapi.com/v1/achApplication/linkplaiditem';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"accountType":"BrokerageCash","accountNumber":"string","dormanAccountId":1,"dormanAccountName":"string","accountId":"string","publicToken":"string"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://live.tradovateapi.com/v1/achApplication/linkplaiditem"

	payload := strings.NewReader("{\n  \"accountType\": \"BrokerageCash\",\n  \"accountNumber\": \"string\",\n  \"dormanAccountId\": 1,\n  \"dormanAccountName\": \"string\",\n  \"accountId\": \"string\",\n  \"publicToken\": \"string\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://live.tradovateapi.com/v1/achApplication/linkplaiditem")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"accountType\": \"BrokerageCash\",\n  \"accountNumber\": \"string\",\n  \"dormanAccountId\": 1,\n  \"dormanAccountName\": \"string\",\n  \"accountId\": \"string\",\n  \"publicToken\": \"string\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://live.tradovateapi.com/v1/achApplication/linkplaiditem")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"accountType\": \"BrokerageCash\",\n  \"accountNumber\": \"string\",\n  \"dormanAccountId\": 1,\n  \"dormanAccountName\": \"string\",\n  \"accountId\": \"string\",\n  \"publicToken\": \"string\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://live.tradovateapi.com/v1/achApplication/linkplaiditem', [
  'body' => '{
  "accountType": "BrokerageCash",
  "accountNumber": "string",
  "dormanAccountId": 1,
  "dormanAccountName": "string",
  "accountId": "string",
  "publicToken": "string"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://live.tradovateapi.com/v1/achApplication/linkplaiditem");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"accountType\": \"BrokerageCash\",\n  \"accountNumber\": \"string\",\n  \"dormanAccountId\": 1,\n  \"dormanAccountName\": \"string\",\n  \"accountId\": \"string\",\n  \"publicToken\": \"string\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "accountType": "BrokerageCash",
  "accountNumber": "string",
  "dormanAccountId": 1,
  "dormanAccountName": "string",
  "accountId": "string",
  "publicToken": "string"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://live.tradovateapi.com/v1/achApplication/linkplaiditem")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```