Delegated trading
Once hosted Connect gives your app a member token, call the existing Trading API at https://app.crosstrade.io/v1/api/tv. Use that member's access token as the bearer credential. The normal endpoint schemas apply, with the delegated-access rules below and operation tracking added to trading commands.
First read: a member's positions
This request needs tv:read (also implied by tv:trade). MEMBER_TOKEN is the access_token from this member's code exchange or refresh, and DEMO12345678 is an account belonging to an identity they shared:
curl -sS "https://app.crosstrade.io/v1/api/tv/accounts/DEMO12345678/positions" \
-H "Authorization: Bearer $MEMBER_TOKEN"
A successful response for an account with no open positions:
{
"success": true,
"data": []
}
There is no extra Partner API wrapper around this call and no member id in the request. The token selects the member. See the code guide for account discovery, Python and JavaScript examples, and the complete client.
Identities and environments
Every account you name must resolve to a Tradovate identity the member selected on the hosted page, in an environment your partner profile allows. Otherwise the request is refused before anything reaches the broker:
| Status | Code | When |
|---|---|---|
| 403 | identity_not_consented | The account belongs to an identity the member did not share with your app |
| 403 | environment_not_allowed | The account is live and your profile is demo-only (or the reverse) |
| 404 | account_not_found | No account with that name is linked to the member |
| 409 | account_ambiguous | Two accounts share that name; use the account id |
Aggregate routes (/accounts, /accounts/snapshot, /status, /positions, /orders, /fills, /fills/history, /fills/order/{orderId}, /cashbalances, /marginsnapshots, /orders/cancelall and /positions/flatten) require the grant to cover all currently linked identities, with every environment allowed. They do not silently filter to shared identities. Partial consent returns 403 identity_not_consented; a disallowed environment returns 403 environment_not_allowed. Use a known, consented account's per-account route instead.
Reference routes (/contracts/find, /contracts/suggest, /exchanges) need tv:read but no account selection. Token, service-policy and entitlement checks still apply. The operation-status route is scoped to commands created for the same member and OAuth client.
Operation matrix
| Operation | Scope | Effect |
|---|---|---|
Supported GET routes, including operation status | tv:read | read |
orders/place, orders/flatplace, positions/reverse, positions/reverseposition, orders/{id}/change, orders/{id}/replace | tv:trade | entry (new or wider risk) |
orders/{id}/cancel, orders/cancel, orders/cancelall | tv:trade | cancel |
positions/close, positions/flatten | tv:trade | reduce |
orders/cancel-and-bracket | tv:trade | protect |
While your service is active or past due within grace, every effect is available. While it is suspended, members keep read, cancel, reduce and protect through your app, but entries answer 403 partner_suspended. A disabled partner has no delegated access. The same rule applies to orders the member's own tools send while your seat funds them: new risk is paused, exits are not.
Request bodies
The route decides the account, path-bound order id and command. Omit those redundant fields from the body. Account and path-bound order-id aliases must agree with the route if supplied. command, destination, delay and key are rejected regardless of value or casing, with 400 validation_failed. Delegated commands run immediately; schedule any delay in your application.
Idempotency
Every mutating Trading API call needs an Idempotency-Key header (1 to 200 printable ASCII characters, without spaces), unique per logical command and never recycled. The key namespace is shared by commands from the same member and OAuth client.
- CrossTrade claims at most one dispatch for a key. A recorded command response carries
operationIdand the headerX-Operation-Id; a rejection before the command is recorded may have neither. - The same key with the same body replays the stored response for 24 hours with
Idempotent-Replayed: true; the same key with a different body is409 idempotency_conflict; a key still in flight is409 idempotency_in_progress. - A positive broker rejection (
4xx) is final and replayable as rejected. A new command needs a new key. - When CrossTrade cannot tell whether the broker received the command (timeout, dropped response, or an ambiguous broker error), the answer is
409 outcome_unknownwithoperationIdandstatusUrl. Do not resend with a new key. Read the account's orders and positions andGET /v1/api/tv/operations/{operationId}with the same member's token. The status response places its record underoperation, includingstate,brokerCorrelation,stepsandreconciliation. States includepending,dispatching,completed,rejectedandoutcome_unknown. For placement-capable commands,brokerCorrelationrecords theclOrdIdbefore dispatch. Composite commands (reverse, flat-and-place, cancel-and-bracket, replace) are one recorded dispatch step, so an unknown outcome still requires inspecting the account.
Placements carry a CrossTrade-generated clOrdId unless you supply one; it is recorded before dispatch and visible on the order in Tradovate.