---
title: Asynchronous Writes
description: Understand why trading writes answer before the exchange decides their outcome, how to read the outcome, and how to retry safely.
---

Every operation documented as an asynchronous write in the [REST API reference](/api-reference/rest) or [WebSocket API reference](/api-reference/websocket) acknowledges submission before the exchange decides its outcome. Account and grant setup can also continue after the response. The references are the source of truth for which operations exist; this page explains how to reconcile any of them.

The acknowledgment therefore confirms acceptance, not the result. Read the result from the resource itself.



## Read the Acknowledgment

**REST**

An accepted write answers `202 Accepted`. The body carries the key you read the outcome with, and each key has one place to read it:

| Write                     | Response key and where to read the outcome                                                                                                                                                                                  |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Place an order            | `order_id`; read [Get Order](/api-reference/rest/orders/get-order).                                                                                                                                                         |
| Amend or cancel an order  | `order_id`; read [Get Order](/api-reference/rest/orders/get-order).                                                                                                                                                         |
| Cancel orders in bulk     | No key; poll [List Orders](/api-reference/rest/orders/list-orders) with `status=resting&status=partially_filled` and the filters you submitted until it returns no orders.                                                  |
| Create an RFQ             | Your `client_rfq_id`; read [List Account RFQs](/api-reference/rest/rfqs/list-account-rfqs) with your `account_id` and filtered by that client identifier.                                                                   |
| Create a quote            | Your `client_quote_id`; read [List Quotes](/api-reference/rest/quotes/list-account-quotes) with your `account_id` and that client identifier.                                                                               |
| Cancel or confirm a quote | `quote_id`; read [Get Quote](/api-reference/rest/quotes/get-quote).                                                                                                                                                         |
| Accept a quote            | `order_id` and `quote_id`; read [Get Quote](/api-reference/rest/quotes/get-quote) for the last-look outcome, then [Get Order](/api-reference/rest/orders/get-order) by the returned `order_id` to reconcile the acceptance. |
| Create an account         | The `pending` account, or your `client_account_id`; read [Get an Account by Client Account ID](/api-reference/rest/portfolio/get-account-by-client-account-id).                                                             |
| Grant account access      | The `pending` grant; read [List Grants](/api-reference/rest/portfolio/list-account-grants).                                                                                                                                 |

The exchange assigns `rfq_id` and `quote_id` only once the RFQ or quote lands, which is why creation echoes your client ID instead.

**WebSocket**

Every write command answers a `*_submitted` frame echoing the command's `id` and the key you read the outcome with: the `order_id` for the [order commands](/api-reference/websocket/orders/receive-create-order-command) (placing also echoes your `client_order_id`), the `quote_id` plus the created order's `order_id` for an [accept](/api-reference/websocket/rfqs/receive-accept-quote-command), the `quote_id` plus your `client_quote_id` for a confirmation or cancellation, and your `client_rfq_id` or `client_quote_id` for [`create_rfq`](/api-reference/websocket/rfqs/receive-create-rfq-command) and [`create_quote`](/api-reference/websocket/rfqs/receive-create-quote-command) — the exchange assigns `rfq_id` and `quote_id` only once the RFQ or quote lands, so their creation echoes your client ID instead. The stream that carries the committed outcome is independent of the command reply, so a stream frame may arrive before the `*_submitted` acknowledgment; correlate by the echoed keys, never by frame order.

For `create_rfq` and `create_quote`, subscribe to the `rfqs` or `quotes` channel before you send, and treat the `rfq_opened` or `quote_opened` frame carrying your `client_rfq_id` or `client_quote_id` as the committed outcome.

A rejected command answers an `error` frame that echoes the command's `id` and leaves the connection open; the command's reference page lists the codes it can send.

## Read the Outcome

**REST**

Poll the resource with bounded backoff until it shows the state you expect.

For account setup, poll [List Accounts](/api-reference/rest/portfolio/list-accounts) until the account is `active`. A `failed` status is not terminal; setup is retried.

Absence and unchanged state are inconclusive. A newly accepted write can take time to become visible, and the exchange can reject a write after accepting it. A rejected order appears with the status `rejected` and a [`rejection_reason`](/concepts/order-entry#rejection-reasons).

The exchange defines no deadline after which absence proves that a write failed. Poll until your own workflow's deadline, then treat the outcome as unresolved. Never submit a replacement under a new client ID because the first write is not yet visible.

**WebSocket**

The [`orders`](/api-reference/websocket/orders/send-order-data) subscription carries an order's outcome and the [`fills`](/api-reference/websocket/orders/send-fill-data) subscription its executions. The [`rfqs`](/api-reference/websocket/rfqs/send-rfq-data) subscription carries an RFQ's outcome. The [`quotes`](/api-reference/websocket/rfqs/send-quote-data) subscription carries a quote's.

Absence is inconclusive. If no frame arrives by your own workflow's deadline, [list your orders](/api-reference/rest/orders/list-orders), [your account's RFQs](/api-reference/rest/rfqs/list-account-rfqs), or [your quotes](/api-reference/rest/quotes/list-account-quotes) over REST filtered by your client ID, then treat the outcome as unresolved. Never submit a replacement under a new client ID because the first write is not yet visible.

## Working State Versus History

Reads of working orders are served from the live working view. This covers [Get Order](/api-reference/rest/orders/get-order) and [List Orders](/api-reference/rest/orders/list-orders) filtered to active statuses only: `status=resting`, `status=partially_filled`, or both.

Every other list, with no `status` filter or with any terminal status (`filled`, `canceled`, `expired`, `rejected`), is served from exchange history. Exchange history converges later than the live working view and is not a snapshot across pages. Use history for terminal accounting, not for completion checks.

After a bulk cancellation, each read proves one thing:

- A `202` proves the exchange accepted the cancellation for processing.
- An empty active-status list proves that every order the exchange has processed is no longer working.
- A history row still reading `resting` once the active-status list is empty is history catching up, not an order still on the book. [Read that order by its ID](/api-reference/rest/orders/get-order) to see its current state from the live working view.

To confirm each cancellation as it commits, subscribe to the WebSocket [`orders`](/api-reference/websocket/orders/send-order-data) channel before you cancel; it delivers one lifecycle frame per state change, `order_canceled` among them.

## Outcome Unknown

**REST**

A `503 Service Unavailable` means the request could not be completed synchronously. For a write, use the problem's `type` to tell these cases apart:

- **The write was never submitted.** The problem has `type: urn:udx:problem:request-admission-unavailable`. Retry later; nothing was submitted.
- **The outcome is unknown.** The problem's `type` names the write and ends in `outcome-unknown`, for example `urn:udx:problem:order-outcome-unknown`. The response carries `Retry-After`. Wait at least that long, then read the outcome as described above before deciding whether to retry.

For a public history read, including account RFQ or quote lists, the problem type `urn:udx:problem:could-not-process-retry` means the read exceeded UDX's upstream deadline. The response carries `Retry-After` and `Cache-Control: no-store`; retry the same read after the advised interval. It does not mean that a write was submitted or that the resource changed.

Do not use `detail` to choose a recovery path.

**WebSocket**

An `error` frame with the code `admission_unavailable` means either the command was never submitted or the submission's outcome could not be confirmed; the code does not distinguish the two. An `internal_error` frame, or a connection that closes before any frame answers the command, leaves the outcome unknown the same way. In every case treat the write as unresolved: reconnect if you must, then read the order, RFQ, or quote over REST or in the snapshot of a new subscription before deciding whether to retry.

Do not use `detail` to choose a recovery path.

## Retry Safely

**REST**

A retry is the identical request: the same method, path, query string, body, client ID, and idempotency key, if one was sent. With the same `UDX-Idempotency-Key`, the exchange returns the original response instead of executing again. Each attempt needs a new timestamp and signature.

The exchange rejects a second order that reuses a `client_order_id` within the same account and market, so an accidental duplicate becomes a `rejected` order rather than a second position. The second request is still accepted with `202`; its `order_id` reads back as `rejected`. The same ID on another market is a new order. RFQ and quote client IDs carry no such protection: a repeated request creates a second RFQ or quote. The exchange rejects a second account that reuses a `client_account_id` within your member with `409 Conflict`; [read the account by that ID](/api-reference/rest/portfolio/get-account-by-client-account-id) to find the one the first request created. Use [idempotency keys](/reference/idempotency) for replay-safe retries of any write.

**WebSocket**

A retry is a new command with a fresh `id` and the same `params`, including the same client ID. The session was signed once at connect, so nothing else changes.

Nothing on the WebSocket deduplicates the retry: unlike REST it has no idempotency-key guard, and `idempotency_key` is carried but not yet deduplicated on. The exchange still rejects a second order that reuses a `client_order_id` within the same account and market, so a duplicated `create_order` becomes a `rejected` order rather than a second position. RFQ and quote client IDs carry no such protection: a resend can create a second RFQ or quote. Retry only once you have confirmed — your orders by `client_order_id`, or the RFQ or quote over REST — that the first write does not exist.
