Skip to main content

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:

StatusCodeWhen
403identity_not_consentedThe account belongs to an identity the member did not share with your app
403environment_not_allowedThe account is live and your profile is demo-only (or the reverse)
404account_not_foundNo account with that name is linked to the member
409account_ambiguousTwo 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​

OperationScopeEffect
Supported GET routes, including operation statustv:readread
orders/place, orders/flatplace, positions/reverse, positions/reverseposition, orders/{id}/change, orders/{id}/replacetv:tradeentry (new or wider risk)
orders/{id}/cancel, orders/cancel, orders/cancelalltv:tradecancel
positions/close, positions/flattentv:tradereduce
orders/cancel-and-brackettv:tradeprotect

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 operationId and the header X-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 is 409 idempotency_conflict; a key still in flight is 409 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_unknown with operationId and statusUrl. Do not resend with a new key. Read the account's orders and positions and GET /v1/api/tv/operations/{operationId} with the same member's token. The status response places its record under operation, including state, brokerCorrelation, steps and reconciliation. States include pending, dispatching, completed, rejected and outcome_unknown. For placement-capable commands, brokerCorrelation records the clOrdId before 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.