Skip to main content

Idempotency

Senticore exposes two separate mechanisms. They are not interchangeable:

MechanismAxisUse
Idempotency-Key headerDurable operation identitySafe HTTP retry for signed-action submit and the delegated order mutations listed below.
clientOrderId (cloid)Order identityStrategy-level order label carried in the signed payload and echoed back on BSL full results, execution streams, and drop-copy where provisioned.

clientOrderId is not a dedup key. It does not collapse duplicate submissions, and the server keeps no (account, clientOrderId) lookup that returns a prior order. Use Idempotency-Key for retry safety; use clientOrderId to correlate your own order id with engine results.

Idempotency-Key (retry safety)

For signed actions and delegated order mutations, send an Idempotency-Key header when a network retry may happen. Send exactly one header containing 1-128 visible ASCII characters (no spaces). Reuse the same key only for the same intended payload.

The delegated contract applies to exactly these eight operations:

  • POST /api/v1/trading/orders
  • DELETE /api/v1/trading/orders/{order_id}
  • POST /api/v1/trading/orders/replace
  • POST /api/v1/trading/orders/batch
  • POST /api/v1/trading/orders/cancel-batch
  • POST /api/v1/institutional/orders
  • POST /api/v1/institutional/orders/batch
  • POST /api/v1/institutional/transfers

On these routes the server binds the key to the tenant/agent, engine account, HTTP method, canonical endpoint, agent nonce, and canonical request hash. An exact retry replays the stored HTTP status and JSON response. Reusing the same key with a different body or nonce returns HTTP 409.

The server durably reserves the operation before consuming the agent nonce. It then persists one immutable engine nonce/timestamp/action plan before enqueueing or applying anything. A concurrent retry waits briefly for the owner; after an expired owner lease, recovery takes over the same plan and checks durable engine truth before enqueueing. Batches persist their complete engine and algo/conditional plan before the first side effect. These are recovery semantics, not a distributed transaction between the control-plane and runtime databases.

clientOrderId (order identity)

clientOrderId is part of the signed ActionPayload. When present it is folded into both the signing hash and the derived OrderId, so two payloads that differ only by clientOrderId sign to different hashes and resolve to different order ids. When absent it is omitted from the canonical bytes (it is not signed as null), which is why existing golden vectors and clients that never set it are unaffected. See Local Action Signing.

On the direct-HTTP/BSL signed-action path and the FIX path, clientOrderId (FIX ClOrdID tag 11) is threaded into the signed payload, echoed in full response-mode actionResults (HotpathOrderResult.clientOrderId), and carried into the private execution stream or drop-copy stream where provisioned.

const payload: LocalActionPayload = {
account,
nonce: nonces.next(),
clientOrderId: "my-strategy-12345",
ts: Date.now(),
action: { kind: "SpotPlaceOrder", market: 14, side: "Bid", price: 79_000_000_000, qty: 1000, timeInForce: "post_only" },
};
const accepted = await client.trading.submitSignedAction(
signAction(payload, key, { chainBinding }),
{ idempotencyKey: `place-${account}-${payload.nonce}` },
);
Agent / delegated path does not carry cloid into the engine

On the agent (delegated) path the signed engine ActionPayload is built with client_order_id = None, so the cloid is not in the signed bytes, the derived OrderId, the engine state, or the state_root. Drop-copy records for agent orders therefore report clientOrderId = null. A clientOrderId you pass on a delegated place-order survives only as off-engine sidecar metadata; do not rely on it round-tripping through engine results on this path.

Retention

Delegated Idempotency-Key operation state and its terminal response are retained for exactly 24 hours from the first reservation. After that deadline a client may reuse the key, so clients must not rely on replay beyond 24 hours. clientOrderId is not retained as a dedup record - it lives only inside the signed payload and the results that echo it.

StateRetention
Delegated idempotency operation and response24 hours from first reservation
Active orderUntil terminal state
Terminal order7 days for reconciliation
Historical fillStandard account history retention

Best practices

  • Generate Idempotency-Key per submitted payload and persist it until a terminal response is known.
  • Never reuse a key for a different intended action or order.
  • Treat an idempotency conflict as "already responded, fetch state".
  • Generate clientOrderId deterministically from your internal order id; use UUID v4 if no deterministic source exists.
  • To correlate engine results back to your order, read clientOrderId from BSL full response-mode actionResults and from the private execution stream or drop-copy (direct-HTTP/BSL and FIX paths only).
  • To cancel or replace, target the engine order_id (the derived OrderId), not the clientOrderId - cloid is a label, not a server-side handle.

Protocol equivalents

clientOrderId is the same signed-payload field across surfaces:

SurfaceField
HTTP (signed action)clientOrderId in the action payload
FIXClOrdID tag 11
BSL compact order entryclient_order_id field in the signed action payload