PearPear
API IntegrationTrade Activity

Transaction Cost Analysis

Measure execution quality against the price you could have had, per fill, per position, or across the account.

Every endpoint on this page needs the pro user role. A basic user gets a 403 with the message Access denied, whatever the credential's scopes are. Read your own role from GET /users/profile (SDK: sdk.core.users.profile) before you build against these endpoints. Roles are basic and pro, and basic is the default.

Transaction cost analysis (TCA) answers one question: how good was that execution, against a benchmark price.

The other reads in this section tell you what happened. TCA tells you what it cost you, against the price you could have had:

To learnRead
What filled, at what price, and the feesFills
What the action did at the venueExecution
What the basket earnedClosed Position
What the fill cost against the arrival priceThis page

Use it to compare execution styles on real orders: whether a TWAP or a ladder beat a plain market entry for your size.

The three endpoints

EndpointScopeReturns
GET /tca/executions/{executionId} (SDK: sdk.core.tca.execution)One action.{ summary?, fillMetrics }. No parameters, no pagination.
GET /tca/positions/{positionId} (sdk.core.tca.position)Every fill on one position, across every action that touched it.{ limit, nextCursor?, fillMetrics }.
GET /tca/summary (sdk.core.tca.summary)The whole account over a date range.{ limit, nextCursor?, summaries, totals }.

All three are scoped to the trade account in x-trade-account-id, and to that account's venue.

Pick by the question. One order you just placed: the execution endpoint. A whole trade, entry through exit: the position endpoint. The cost trend across the account: the summary endpoint.

When metrics exist

Pear records a benchmark snapshot before it submits each leg, then computes the metric after fill sync confirms the fill with the venue. So a metric appears some time after the fill does, not with it.

Two consequences:

  • A fresh execution has no TCA yet. Poll, or read TCA on a later pass rather than in the same loop that places the order.
  • A fill with no benchmark never gets a metric. That covers fills imported from outside Pear, and any fill whose benchmark snapshot did not survive. Those fills stay on Fills and never appear here.

status is FINAL on every row these endpoints return. There is no preliminary state on the wire.

Read one execution

curl "https://pro-gateway.pearprotocol.io/tca/executions/$EXECUTION_ID" \
  -H "x-api-key: $PEAR_API_KEY" \
  -H "x-trade-account-id: $TRADE_ACCOUNT_ID"
{
  "summary": {
    "id": "0a5f8c31-2d47-4be9-91c0-7f3a6d8e2b45",
    "executionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "tradeAccountId": "5e7a1b20-8c34-4d19-b6f0-a1c2d3e4f567",
    "connector": "hyperliquid",
    "executionContext": {
      "intent": "OPEN",
      "orderType": "MARKET",
      "executionStatus": "COMPLETED",
      "positionId": "9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789",
      "legs": [
        { "symbol": "0", "side": "BUY", "reduceOnly": false },
        { "symbol": "1", "side": "SELL", "reduceOnly": false }
      ]
    },
    "notionalQuote": "999.947",
    "notionalUsd": "999.947",
    "weightedSlippageBps": "4.9533",
    "totalSlippageUsd": "0.49536",
    "totalTradeFee": "0.44",
    "totalPearFee": "0.1",
    "totalCostUsd": "1.03536",
    "fillCount": 2,
    "status": "FINAL",
    "createdAt": "2026-01-01T00:02:14.880Z",
    "updatedAt": "2026-01-01T00:02:14.880Z"
  },
  "fillMetrics": [
    {
      "id": "b41c9d02-7e58-4a13-9c6f-0d2e8b7a4f31",
      "fillId": "7d3e91a4-5c62-4f08-b1d7-2e9a4c6f8035",
      "benchmarkId": "c7e0a51d-9b34-42f8-8a17-5d6c3e9b2074",
      "executionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "positionId": "9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789",
      "tradeAccountId": "5e7a1b20-8c34-4d19-b6f0-a1c2d3e4f567",
      "connector": "hyperliquid",
      "symbol": "0",
      "cloid": "0x9f2c4a7e10b3486d",
      "exchangeFillId": "42917338",
      "slippageBps": "4.4013",
      "slippageQuote": "0.21996",
      "slippageUsd": "0.21996",
      "implementationShortfallBps": "5.2549",
      "spreadCostQuote": "0.03172",
      "spreadCostUsd": "0.03172",
      "tradeFee": "0.22",
      "pearFee": "0.05",
      "totalCostUsd": "0.48996",
      "gatewayLatencyMs": 412,
      "timeToFillMs": 640,
      "quoteAsset": "USDC",
      "notionalQuote": "499.9826",
      "notionalUsd": "499.9826",
      "submittedAt": "2026-01-01T00:00:00.000Z",
      "acknowledgedAt": "2026-01-01T00:00:00.412Z",
      "exchangeExecutedAt": "2026-01-01T00:00:00.640Z",
      "timingGranularity": "BATCH",
      "status": "FINAL",
      "createdAt": "2026-01-01T00:02:14.612Z",
      "updatedAt": "2026-01-01T00:02:14.612Z",
      "benchmark": {
        "id": "c7e0a51d-9b34-42f8-8a17-5d6c3e9b2074",
        "executionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "orderId": "8d2e5b91-0c44-4a7e-9f31-6b0d5e2a1c88",
        "cloid": "0x9f2c4a7e10b3486d",
        "legIndex": 0,
        "connector": "hyperliquid",
        "symbol": "0",
        "side": "BUY",
        "decisionPrice": "96100",
        "arrivalMid": "96108.2",
        "arrivalBid": "96102.1",
        "arrivalAsk": "96114.3",
        "markPrice": "96110.4",
        "source": "mid",
        "timestamp": "2025-12-31T23:59:59.870Z",
        "quoteAsset": "USDC",
        "usdConversionPrice": "1",
        "createdAt": "2025-12-31T23:59:59.930Z"
      }
    }
  ]
}

The second leg's metric is omitted above. A real response carries one entry in fillMetrics per confirmed fill, newest metric first.

This endpoint returns 200 with summary absent when no TCA rows exist, not a 404. Check for the key, not the status code. An execution id from another venue, or one that does not exist, reads the same way: 200, no summary, and an empty fillMetrics.

summary is derived from the same rows as fillMetrics, so summary.fillCount matches the length of fillMetrics once the execution has finished syncing.

Provenance lives here and nowhere else

summary.executionContext carries positionId, scheduleId, and triggerId. This is the only place the API exposes what an execution belongs to. GET /executions withholds all three, so an OPEN execution read there does not name the position it created. See Execution.

FieldPresent when
positionIdThe execution acted on, or created, a position.
scheduleIdThe execution was a slice of a TWAP schedule.
triggerIdThe execution was a fired trigger, including a ladder rung.

That makes the summary endpoint a way to correlate a whole account's executions back to their positions, at the cost of the pro role.

Read one position

curl "https://pro-gateway.pearprotocol.io/tca/positions/$POSITION_ID?limit=100" \
  -H "x-api-key: $PEAR_API_KEY" \
  -H "x-trade-account-id: $TRADE_ACCOUNT_ID"
{
  "limit": 100,
  "fillMetrics": [
    {
      "id": "e93a7b18-4c25-4d60-8f19-6b2d7e0a5c34",
      "fillId": "2f8c40d7-6e13-4a95-b072-8d1e5f3a9c60",
      "benchmarkId": "4d1b6e93-0a72-4c58-9e31-7f2c5a8d6b04",
      "executionId": "6b09e2f4-1d83-4c57-a920-3e8f5b7d1a26",
      "positionId": "9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789",
      "tradeAccountId": "5e7a1b20-8c34-4d19-b6f0-a1c2d3e4f567",
      "connector": "hyperliquid",
      "symbol": "1",
      "cloid": "0x51b7e3c8a94d2f60",
      "exchangeFillId": "43102994",
      "slippageBps": "-1.6192",
      "slippageQuote": "-0.081",
      "slippageUsd": "-0.081",
      "implementationShortfallBps": "-3.5616",
      "spreadCostQuote": "0.1134",
      "spreadCostUsd": "0.1134",
      "tradeFee": "0.22",
      "pearFee": "0.05",
      "totalCostUsd": "0.189",
      "gatewayLatencyMs": 388,
      "timeToFillMs": 601,
      "quoteAsset": "USDC",
      "notionalQuote": "500.1588",
      "notionalUsd": "500.1588",
      "submittedAt": "2026-01-03T09:14:02.000Z",
      "acknowledgedAt": "2026-01-03T09:14:02.388Z",
      "exchangeExecutedAt": "2026-01-03T09:14:02.601Z",
      "timingGranularity": "BATCH",
      "status": "FINAL",
      "createdAt": "2026-01-03T09:16:41.207Z",
      "updatedAt": "2026-01-03T09:16:41.207Z",
      "benchmark": {
        "id": "4d1b6e93-0a72-4c58-9e31-7f2c5a8d6b04",
        "executionId": "6b09e2f4-1d83-4c57-a920-3e8f5b7d1a26",
        "orderId": "5a83f16c-2b47-4e09-97d1-8c6b0a3e4f52",
        "cloid": "0x51b7e3c8a94d2f60",
        "legIndex": 1,
        "connector": "hyperliquid",
        "symbol": "1",
        "side": "BUY",
        "decisionPrice": "3088.5",
        "arrivalMid": "3087.9",
        "arrivalBid": "3087.2",
        "arrivalAsk": "3088.6",
        "markPrice": "3088.1",
        "source": "mid",
        "timestamp": "2026-01-03T09:14:01.860Z",
        "quoteAsset": "USDC",
        "usdConversionPrice": "1",
        "createdAt": "2026-01-03T09:14:01.912Z"
      }
    }
  ]
}

One of six entries is shown. nextCursor is absent, so this is the last page. The entry above is the second leg of the execution that closed the position; the same execution appears as a summary in the next section.

This endpoint returns every fill metric on the position: the open, every adjust, every rebalance, and the close. There is no summary object, because a position spans many executions.

Ordering is by when Pear recorded the metric, newest first. That is close to fill order but not identical, because sync confirms fills in batches. Sort on exchangeExecutedAt yourself if you need true venue order.

Read the account

curl "https://pro-gateway.pearprotocol.io/tca/summary?from=2026-01-01T00:00:00.000Z&to=2026-02-01T00:00:00.000Z&limit=100" \
  -H "x-api-key: $PEAR_API_KEY" \
  -H "x-trade-account-id: $TRADE_ACCOUNT_ID"
{
  "limit": 100,
  "summaries": [
    {
      "id": "0a5f8c31-2d47-4be9-91c0-7f3a6d8e2b45",
      "executionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "tradeAccountId": "5e7a1b20-8c34-4d19-b6f0-a1c2d3e4f567",
      "connector": "hyperliquid",
      "executionContext": {
        "intent": "OPEN",
        "orderType": "MARKET",
        "executionStatus": "COMPLETED",
        "positionId": "9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789",
        "legs": [
          { "symbol": "0", "side": "BUY", "reduceOnly": false },
          { "symbol": "1", "side": "SELL", "reduceOnly": false }
        ]
      },
      "notionalQuote": "999.947",
      "notionalUsd": "999.947",
      "weightedSlippageBps": "4.9533",
      "totalSlippageUsd": "0.49536",
      "totalTradeFee": "0.44",
      "totalPearFee": "0.1",
      "totalCostUsd": "1.03536",
      "fillCount": 2,
      "status": "FINAL",
      "createdAt": "2026-01-01T00:02:14.880Z",
      "updatedAt": "2026-01-01T00:02:14.880Z"
    },
    {
      "id": "8f26b0c5-3e91-4d74-a58b-1c0f7e6d2a93",
      "executionId": "6b09e2f4-1d83-4c57-a920-3e8f5b7d1a26",
      "tradeAccountId": "5e7a1b20-8c34-4d19-b6f0-a1c2d3e4f567",
      "connector": "hyperliquid",
      "executionContext": {
        "intent": "CLOSE",
        "orderType": "MARKET",
        "executionStatus": "COMPLETED",
        "positionId": "9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789",
        "legs": [
          { "symbol": "0", "side": "SELL", "reduceOnly": true },
          { "symbol": "1", "side": "BUY", "reduceOnly": true }
        ]
      },
      "notionalQuote": "1001.2508",
      "notionalUsd": "1001.2508",
      "weightedSlippageBps": "1.2075",
      "totalSlippageUsd": "0.12089",
      "totalTradeFee": "0.44",
      "totalPearFee": "0.1",
      "totalCostUsd": "0.66089",
      "fillCount": 2,
      "status": "FINAL",
      "createdAt": "2026-01-03T09:16:41.455Z",
      "updatedAt": "2026-01-03T09:16:41.455Z"
    }
  ],
  "totals": {
    "executionCount": 37,
    "fillCount": 74,
    "notionalQuote": "38412.6094",
    "notionalUsd": "38412.6094",
    "weightedSlippageBps": "3.8103",
    "totalSlippageUsd": "14.63652",
    "totalTradeFee": "16.28",
    "totalPearFee": "3.7",
    "totalCostUsd": "34.61652"
  }
}

Two of 37 summaries are shown. One summary exists per execution, and it is refreshed in place whenever new fills for that execution confirm.

totals covers the whole date window, not the page. With 37 executions in the window and limit=100, one page holds them all; with limit=10, totals still reports all 37. Do not sum the page and expect totals.

The window is from and to

/tca/summary uses from and to. The portfolio endpoints use startDate and endDate for the same idea. Sending the wrong pair is not an error. Unknown query parameters are dropped before the handler sees them, so ?startDate=… returns the unbounded result and no warning. See Trade History.

Both bounds are inclusive of the boundary instant, and both are optional. Absent means unbounded on that side.

The window filters on when the summary was recorded, not when you traded. A trade placed just before to whose fills confirmed just after it falls outside the window. Widen to past the end of the period you care about, then filter on executionContext yourself if you need trade-time precision.

Query parameters

ParameterEndpointsValues
limitPositions, summaryPositive integer. Default 100, maximum 2000. Above the maximum is a 400, not a clamp.
cursorPositions, summaryThe previous page's nextCursor.
from, toSummaryISO 8601 datetime bounds.

GET /tca/executions/{executionId} takes no query parameters at all.

The cursor is a datetime, not an opaque token

Other endpoints in this section tell you to treat the cursor as opaque. These two validate it. cursor must parse as an ISO 8601 datetime in UTC, of the form 2026-01-03T09:16:41.207Z. Anything else is a 400, not an empty page.

GET /tca/positions/9c1b2d3e-4f56-4789-a0b1-c2d3e4f56789?limit=2&cursor=2026-01-03T09:16:41.207Z
{
  "limit": 2,
  "nextCursor": "2026-01-02T17:41:08.330Z",
  "fillMetrics": []
}

Only the envelope matters here; a real page carries two entries in fillMetrics.

The rules are the usual ones: pass nextCursor back verbatim as cursor, and stop when nextCursor is absent rather than when a page is short.

The page is taken strictly before the cursor instant. Metrics written by the same sync batch share one createdAt to the millisecond, so a page boundary that falls inside a batch drops the rest of that batch. Keep limit comfortably above the number of fills a single sync confirms — the default 100 covers any ordinary basket — and read a position in one request rather than paging through it.

Response fields

Execution summary

One row per execution. Returned by /tca/summary in summaries, and by /tca/executions/{executionId} as summary.

FieldTypeMeaning
idUUIDThe summary row.
executionIdUUIDThe execution it describes.
tradeAccountIdUUIDThe trade account that owns it.
connectorEnumThe venue: hyperliquid, binance, bybit, okx, or lighter.
executionContextObjectThe execution's intent, status, legs, and provenance. See below.
notionalQuoteDecimal stringFilled notional in the quote asset, summed over the legs.
notionalUsdDecimal stringThe same in USD. Absent without a USD conversion.
weightedSlippageBpsDecimal stringNotional-weighted slippage across the legs, in basis points.
totalSlippageUsdDecimal stringSlippage cost in USD, summed over the legs.
totalTradeFeeDecimal stringThe venue's fees, in USD.
totalPearFeeDecimal stringPear's fees, in USD. See Trading Fees.
totalCostUsdDecimal stringSlippage plus both fees, in USD.
fillCountNumberFill metrics folded into this summary.
statusEnumAlways FINAL.
createdAtISO datetimeWhen the summary was first recorded. This is what from and to filter on.
updatedAtISO datetimeWhen it was last refreshed.

Every decimal field above is optional. It is absent, not "0", when no leg contributed a value.

Execution context

summary.executionContext. Absent only if the execution row behind the summary has gone.

FieldTypeMeaning
intentEnumOPEN, CLOSE, ADJUST, or REVERSE.
orderTypeEnumMARKET.
executionStatusEnumIDLE, ACTIVE, COMPLETED, FAILED, or CANCELLED.
positionIdUUIDThe position the execution acted on or created. Optional.
scheduleIdUUIDThe TWAP schedule this execution was a slice of. Optional.
triggerIdUUIDThe trigger that fired it, including a ladder rung. Optional.
legsArrayThe planned basket: symbol, side, reduceOnly per leg.

symbol is the instrument ID, not the ticker. See Instrument ID.

Fill metric

One row per confirmed fill. Returned by both /tca/executions/{executionId} and /tca/positions/{positionId} in fillMetrics.

FieldTypeMeaning
idUUIDThe metric row.
fillIdUUIDThe fill it measures.
benchmarkIdUUIDThe benchmark snapshot it measured against. Optional.
executionIdUUIDThe execution the fill came from. Optional.
positionIdUUIDThe position the fill belongs to. Optional.
tradeAccountIdUUIDThe trade account that owns it.
connectorEnumThe venue.
symbolStringThe instrument ID.
cloidStringThe client order id Pear sent to the venue. Optional.
exchangeFillIdStringThe venue's own id for the print. Optional.
slippageBpsDecimal stringFill price against arrivalMid, in basis points, signed by side.
slippageQuoteDecimal stringThe same in quote-asset units, for this fill's quantity.
slippageUsdDecimal stringThe same in USD.
implementationShortfallBpsDecimal stringFill price against decisionPrice, in basis points, signed by side. Absent without a decision price.
spreadCostQuoteDecimal stringWhat crossing the arrival spread cost, in quote-asset units. Absent without an arrival bid and ask.
spreadCostUsdDecimal stringThe same in USD.
tradeFeeDecimal stringThe venue's fee for this fill, in USD.
pearFeeDecimal stringPear's fee for this fill, in USD.
totalCostUsdDecimal stringslippageUsd + tradeFee + pearFee.
gatewayLatencyMsNumberacknowledgedAt minus submittedAt.
timeToFillMsNumberexchangeExecutedAt minus submittedAt.
quoteAssetStringThe quote asset the *Quote fields are denominated in.
notionalQuoteDecimal stringFill quantity × fill price, in the quote asset.
notionalUsdDecimal stringThe same in USD.
submittedAtISO datetimeWhen Pear sent the request.
acknowledgedAtISO datetimeWhen the venue's response came back.
exchangeExecutedAtISO datetimeWhen the venue executed the fill.
timingGranularityEnumBATCH or LEG. What the three timestamps above measure.
statusEnumAlways FINAL.
createdAtISO datetimeWhen the metric was recorded. This is what cursor pages on.
updatedAtISO datetimeWhen it was last recomputed.
benchmarkObjectThe benchmark snapshot. Optional. See below.

Everything from slippageBps through timingGranularity is optional, alongside the fields marked Optional above. Absent means not measurable, not zero — a missing arrival quote leaves the spread fields out entirely rather than reporting "0".

timingGranularity: "BATCH" means the timings cover the whole basket submission, not this leg. Pear submits a basket in one call and times that call, so every leg of the basket carries the same submittedAt, acknowledgedAt, and gatewayLatencyMs. LEG marks a per-leg measurement. Today every metric reports BATCH. Compare gatewayLatencyMs across executions, not across the legs of one.

Benchmark

fillMetric.benchmark. The price snapshot Pear took just before submitting the leg.

FieldTypeMeaning
idUUIDThe benchmark row. Matches the metric's benchmarkId.
executionIdUUIDThe execution it was captured for.
orderIdUUIDThe venue order it was attached to. Optional.
cloidStringThe client order id. Optional.
legIndexNumberZero-based index of the leg in the plan.
connectorEnumThe venue.
symbolStringThe instrument ID.
sideEnumBUY or SELL, as planned.
decisionPriceDecimal stringThe price when the strategy chose to build this leg. Optional in the schema, present in practice.
arrivalMidDecimal stringMid price when the leg was prepared.
arrivalBidDecimal stringBest bid at the same instant. Optional.
arrivalAskDecimal stringBest ask at the same instant. Optional.
markPriceDecimal stringMark price at the same instant. Optional.
indexPriceDecimal stringIndex price. Not populated today.
sourceStringWhich feed priced the leg: mid or mark.
timestampISO datetimeWhen the price was observed.
quoteAssetStringThe instrument's quote asset. Optional.
usdConversionPriceDecimal stringQuote-to-USD rate used for the USD fields. Optional.
createdAtISO datetimeWhen the snapshot was stored.

usdConversionPrice is "1" for a stable quote — USD, USDC, or USDT — and absent otherwise. When it is absent, every USD field on the metric is absent too, and only the *Quote fields carry numbers. That is deliberate: Pear reports quote-denominated cost rather than guessing a conversion.

Summary totals

totals on /tca/summary, aggregated over the whole from/to window.

FieldTypeMeaning
executionCountNumberExecutions with a summary in the window.
fillCountNumberFill metrics behind them.
notionalQuoteDecimal stringTotal filled notional in quote assets.
notionalUsdDecimal stringThe same in USD.
weightedSlippageBpsDecimal stringNotional-weighted slippage across every execution in the window.
totalSlippageUsdDecimal stringTotal slippage cost in USD.
totalTradeFeeDecimal stringTotal venue fees in USD.
totalPearFeeDecimal stringTotal Pear fees in USD.
totalCostUsdDecimal stringSlippage plus both fees, in USD.

executionCount and fillCount are always present, and 0 on an empty window. The decimal fields are absent when nothing contributed.

weightedSlippageBps weights by USD notional. It falls back to quote notional only when no execution in the window has a USD notional at all.

notionalQuote sums across quote assets. On an account trading one stable quote it is meaningful. Across mixed quotes it adds unlike units — use notionalUsd for any cross-asset view.

How to read the numbers

The sign is a verdict

Every cost figure is signed by side, so the sign means the same thing on a long leg and a short one:

  • Positive is cost. You bought above the benchmark, or sold below it.
  • Negative is price improvement. You bought below the benchmark, or sold above it.

A negative slippageUsd pulls totalCostUsd down, and totalCostUsd itself can be negative when price improvement exceeds the fees.

Unlike position and closed-trade fields, these decimal strings carry no leading +. A positive slippage reads "4.4013", not "+4.4013".

Slippage versus implementation shortfall

The two differ only in the benchmark they measure against.

MetricBenchmarkMeasures
slippageBpsarrivalMid — the mid when Pear prepared the leg, an instant before submitting.What happened between submitting and filling: crossing the spread, queue, and impact.
implementationShortfallBpsdecisionPrice — the price when the strategy chose the trade.The same, plus whatever the price moved while the order was being built.

Read slippageBps to judge the venue and your order type. Read implementationShortfallBps to judge the whole path from decision to fill. The gap between them is delay cost: price drift you paid for before the order ever reached the venue.

Both are computed as the signed price difference over the benchmark, in basis points. Both are absent when their benchmark price is missing.

What a benchmark price means

A benchmark is a snapshot, not a quote you could have traded on. Pear records it from the price feed while preparing the leg, and source names the feed: mid or mark. timestamp is when the price was observed, so the gap between it and submittedAt tells you how stale the benchmark was by the time the order went out.

arrivalMid is the fair midpoint. arrivalBid and arrivalAsk are the book you would actually have crossed. That is why spread cost is separate:

  • spreadCostQuote is the unavoidable part — half the spread, on your quantity. A marketable order pays it by definition.
  • slippageQuote minus spreadCostQuote is the avoidable part — impact, queue position, and drift during the round trip.

If slippage is consistently far above spread cost, your order is too large for the book at that instant. Slice it: see TWAP.

The two latencies

They start at the same instant and end at different ones, so read them apart:

  • gatewayLatencyMs — submit to acknowledgement. Pear's own path plus the venue accepting the order. Slow here is infrastructure.
  • timeToFillMs — submit to venue execution. Adds matching, queueing, and partial-fill delay. Slow here is the market.

timeToFillMs is never smaller than gatewayLatencyMs in normal operation. On older rows that predate explicit exchange execution timestamps, exchangeExecutedAt falls back to when Pear recorded the fill, which inflates timeToFillMs. Trust the ordering of timeToFillMs values within a run rather than the absolute number across a long history.

What gets rejected

StatusCause
400limit above 2000, limit not a positive integer, or cursor, from, or to not an ISO 8601 datetime. The body is the validation shape, with details[].path naming the parameter.
400x-trade-account-id missing, or not a UUID.
401Missing or invalid credentials.
403The user's role is basic. The message is Access denied.
404x-trade-account-id names a trade account that is not yours.

No TCA endpoint returns 404 for an unknown execution or position id. An id that does not exist, or belongs to another venue, returns 200 with an empty result. Branch on the payload. Send a well-formed UUID either way: the path id is not validated before it reaches the database.

A read-scope API key is enough. TCA is read-only, so the write-scope check never applies. See Error Handling.

Basket-scoped, not asset-scoped

Every benchmark is captured per leg of the basket you asked for, and every cost is measured against that leg's own arrival price. Expect the numbers to differ from the venue's own execution reports, which measure against the asset in isolation. See Synthetic Position.

On this page