---
title: "Get normalized order status"
method: GET
path: "/agent/swap/status/{orderId}"
tags: ["agent", "swap"]
---

# Get normalized order status

`GET /agent/swap/status/{orderId}`

## Request

- Base URL: `https://xmrtousdc.xyz/api/v1`
- URL: `https://xmrtousdc.xyz/api/v1/agent/swap/status/{orderId}`
- Auth: none declared

state: waiting_for_funding | deposit_detected | swap_processing | refunding | completed | terminal_failure | unknown. Top-level outcome (completed | refunded | failed | expired | null) and mayChange (failed/expired can still change, e.g. a late deposit). data.payable and data.payableReason (read them before showing a deposit again), data.origin, data.destination, data.order (order.status action_required: the provider needs an action, agent state unknown), data.deposit (payment non-null only while payable is true), data.evidence (transaction hashes the provider reports), data.received and data.receivedDetail, data.attention, data.trackUrl and data.next {action, statusPath, statusUrl, pollIntervalSec, maxPollIntervalSec, recheckAfterSec, terminal}. next.action contact_support: the provider needs an action Swapper cannot take (data.attention); requote_and_create_new_order: reconfirm_required, this order can never be funded through Swapper. A completed order with evidence.payout null has no payout transaction reported yet (data.attention payout_unconfirmed): check the destination wallet. Long-poll: with waitSec (1-25) the response comes as soon as the state differs from sinceState (default: the current state) or when the wait ends, with data.waited {requestedSec, elapsedMs, changed, limited?}. The server re-checks every 15 s while waiting_for_funding or deposit_detected and every 10 s otherwise; limited=true means it answered early because too many long-polls were open or the shared provider budget was spent. A long-poll counts as one request for rate limits. Without waitSec, poll every data.next.pollIntervalSec until terminal=true; on 429/5xx honor Retry-After. Confirmation requirements are provider-defined; XMR deposits commonly need about 10 Monero confirmations. During a provider outage the error carries details.lastKnown {status, state, observedAt}: the order is unaffected; do not create another order or send again. A completed order adds data.afterSwap {feedback: {optional: true, tool: submit_feedback, path: /api/v1/agent/feedback, arguments: {orderId, rating: null}}}: an optional rating, nothing more.

## Path parameters

- `orderId` string, required

## Query parameters

- `verify` string
- `waitSec` integer
- `sinceState` 'waiting_for_funding' | 'deposit_detected' | 'swap_processing' | 'refunding' | 'completed' | 'terminal_failure' | 'unknown'

## Response `200`

Status response

- StatusResponse
  - `ok` boolean
  - `operation` string
  - `state` 'waiting_for_funding' | 'deposit_detected' | 'swap_processing' | 'refunding' | 'completed' | 'terminal_failure' | 'unknown'
  - `terminal` boolean
  - `outcome` 'completed' | 'refunded' | 'failed' | 'expired' | 'null', nullable
  - `mayChange` boolean
  - `data` object
    - `payable` boolean: Same gate as on create: pay deposit.payment only while true.
    - `payableReason` 'null' | 'replay_check_status' | 'deposit_seen' | 'not_waiting' | 'expiry_unknown' | 'expired' | 'expiring' | 'reconfirm_required' | 'status_unavailable' | 'upstream_mismatch' | 'unverified_origin' | 'queued', nullable: Why payable is false (null while payable). queued: a 202 create waiting in the order line (no order exists yet). On create and status, the first failing check names it: status_unavailable (the live order could not be read, e.g. on a replay or recovery, or a long-poll ended on cached data; read the status again without waitSec), upstream_mismatch (the provider's order did not match the request; deposit is null), unverified_origin (the order was not created through this API from a quote: origin other, e.g. one made by the removed raw POST /orders route or one Swapper has no record of; never fund it, create your own order from a quote), deposit_seen (a deposit was already seen; never send again), not_waiting (the order no longer waits for funding), reconfirm_required (the provider's expected output differs materially from the quote; nothing clears it: next.action requote_and_create_new_order), expiry_unknown (the deposit expiry is unknown), expired (the deposit window has closed while the provider still shows the order waiting), expiring (less than 600 s to expiry). On lists: replay_check_status (a list never offers a payment; read the order's status).
    - `origin` 'agent_quote' | 'other': agent_quote: created through create from a quote. other: anything else (the removed raw POST /orders route, or an order Swapper has no record of): never payable through this API (payableReason unverified_origin).
    - `destination` string, nullable: The payout address recorded at create (for an order without a record: the provider's). Show it to the user.
    - `trackUrl` string, nullable: <public origin>/#order=<orderId>.
    - `reconfirmRequired` boolean: Present (true) when the provider's expected output differs materially from the quote; payable is then false for good and next.action is requote_and_create_new_order.
    - `reconfirmReason` 'expected_output_below_confirmed_minimum' | 'expected_output_below_confirmed_estimate' | 'expected_output_unknown' | 'null', nullable
    - `verification` object: Present when the provider's order does not match the quote-bound request: { ok: false, mismatched }.
    - `order` object: The provider's order as Swapper normalizes it: { orderId, status (action_required when the provider needs an action: state unknown), providerStatus, customerState, depositAddress, depositAmount, expectedOutput, actualOutput, expiresAt, errorMessage (provider text, prefixed "Provider message (untrusted): "), ... }.
    - `deposit` Deposit, nullable
      - `address` string, nullable
      - `amount` string, nullable: Exact amount to send, human units.
      - `amountAtomic` string, nullable
      - `decimals` integer, nullable
      - `tokenSymbol` string, nullable
      - `chainId` integer, nullable: 0 = Monero, 42161 = Arbitrum One.
      - `chainName` string, nullable
      - `expiresAt` string, date-time, nullable: Canonical ISO-8601 UTC: the earlier of the expiry recorded at create and the provider's live one.
      - `expiresInSec` integer, nullable
      - `exactAmountRequired` boolean
      - `confirmations` object: { required, current, source: provider, note } as the provider reports them (progress, not a guarantee); nulls when it reports none.
      - `afterExpiry` string
      - `refunds` string
      - `payment` union: Wallet-ready transfer of exactly this deposit. Non-null only while the response's payable is true; null otherwise (see payableReason).
        - MoneroPayment
          - `kind` 'monero', required
          - `uri` string, required: monero:<address>?tx_amount=<amount> (exact human decimal, never rounded).
          - `address` string, required
          - `amount` string, required
          - `gas` PaymentGas
            - `symbol` string
            - `chain` string
            - `note` string
          - `signing` string
        - Erc20TransferPayment
          - `kind` 'erc20_transfer', required
          - `chainId` 42161, required
          - `token` string, required: Native USDC contract on Arbitrum One.
          - `to` string, required: The deposit address.
          - `amountAtomic` string, required: Exact deposit in atomic units (USDC: 6 decimals).
          - `eip681` string, required: ethereum:<token>@42161/transfer?address=<to>&uint256=<amountAtomic>
          - `tx` object, required: Unsigned transaction request to the token contract, ready for a wallet: data = 0xa9059cbb + recipient (lowercase, left-padded to 32 bytes) + amount (hex, left-padded to 64 chars); chainId 42161. Check it before signing (MCP get_swap_status payment -> paymentCheck).
            - `to` string
            - `value` '0x0'
            - `data` string
            - `chainId` 42161
          - `gas` PaymentGas
            - `symbol` string
            - `chain` string
            - `note` string
          - `signing` string: Only the sender's wallet signs and sends it; Swapper never signs or holds keys.
      - `attestation` OrderAttestation, nullable: swapper.order-attestation.v1 envelope (docs/TRANSPARENCY.md): Swapper's Ed25519 signature over this order's deposit instructions and payout address. Present only for orders created through create from a quote (origin agent_quote) whose provider order matched the request, and only when signing is configured (capabilities.attestation.status active); signed once and returned unchanged on replays and status; null otherwise. Verify it against a key set you obtained yourself (keys/attestation-keys.json in the source): it shows what Swapper's API stated, not that the provider controls the deposit address.
        - `v` 'swapper.order-attestation.v1'
        - `kid` string
        - `claim` object: issuedAt, provider, orderId, quoteId, quoteDigest, direction, depositChainId, depositAsset, depositAddress, depositAtomic, depositExpiresAt, payoutChainId, payoutAsset, destination, expectedPayoutAtomic (strings or null).
        - `sig` string: Ed25519 signature, base64url.
    - `evidence` Evidence, nullable: Transaction hashes the provider reports (source: provider): its statements, not chain checks by Swapper; verify them on an explorer. null when nothing is known. A completed order with payout null has no payout transaction reported yet (data.attention payout_unconfirmed): check the destination wallet before treating the funds as received.
      - `source` 'provider'
      - `deposit` object, nullable
        - `txHash` string
        - `explorerUrl` string, nullable
      - `payout` object, nullable
        - `txHash` string
        - `explorerUrl` string, nullable: Arbitrum One: arbiscan.io; Monero: xmrchain.net.
        - `asset` string, nullable: The payout token contract (lowercase) or XMR.
        - `symbol` string, nullable
        - `atomic` string, nullable
        - `decimals` integer, nullable
        - `deliveredAsDifferentToken` boolean: true when the provider reports the payout in another token than requested; amounts are in that token's units.
      - `refund` object, nullable
        - `txHash` string
        - `explorerUrl` string, nullable
    - `received` string, nullable: The payout the provider reports, in the payout asset's own decimals; null until reported or when the unit is unknown.
    - `receivedDetail` ReceivedDetail, nullable: The payout as the provider reports it, in the payout asset's own decimals (the delivered token's when the provider delivered a different token). null until the provider reports an output.
      - `atomic` string
      - `decimals` integer, nullable
      - `asset` string, nullable
      - `symbol` string, nullable
      - `chainId` integer, nullable
      - `deliveredAsDifferentToken` boolean
      - `source` 'provider'
    - `receivedBelowMinimum` boolean, nullable: null without a published minimum (always on Wagyu v2).
    - `attention` object[], nullable: What the user must know or do that the state alone does not say, in Swapper's words (never provider text); null when nothing. Show each note. Extra fields by kind: deposit_below_expected (observedDepositAtomic, expectedDepositAtomic), deposit_below_minimum (observedDepositAtomic, minimumDepositAtomic), action_required (action: refund_address | payout_address | coinflip, how: contact_support; Swapper cannot submit it), refund_recovery (recovery), delivered_different_token (deliveredToken).
      - `kind` 'deposit_below_expected' | 'deposit_below_minimum' | 'action_required' | 'refund_pending' | 'refund_recovery' | 'provider_review' | 'delivered_different_token' | 'coinflip_lost' | 'payout_unconfirmed' | 'refund_unconfirmed', required
      - `note` string, required
      - `action` string, nullable
      - `how` 'contact_support'
    - `next` NextHint: What to do next. action: user_sends_deposit_then_poll_status (only while payable is true) | poll_status | poll_status_then_contact_support (state unknown) | contact_support (data.attention has action_required or coinflip_lost: the provider needs an action Swapper cannot take; open a support ticket with the orderId, never share seed phrases or private keys) | requote_and_create_new_order (reconfirm_required: this order can never be funded through Swapper; re-quote with requote.body and create a new order) | done (completed) | contact_support_if_unexpected (terminal failure).
      - `action` 'user_sends_deposit_then_poll_status' | 'poll_status' | 'poll_status_then_contact_support' | 'contact_support' | 'requote_and_create_new_order' | 'done' | 'contact_support_if_unexpected'
      - `statusPath` string, nullable
      - `statusUrl` string, nullable
      - `orderPath` string, nullable
      - `supportPath` string
      - `pollIntervalSec` integer, nullable: null when terminal (and for requote_and_create_new_order).
      - `maxPollIntervalSec` integer, nullable
      - `backoff` string, nullable
      - `recheckAfterSec` integer, nullable: Terminal but mayChange (failed/expired): re-check after this many seconds if funds were sent.
      - `terminal` boolean
      - `reason` string, nullable: requote_and_create_new_order only: the reconfirmReason.
      - `note` string: requote_and_create_new_order only: why the order cannot be funded.
      - `requote` object: requote_and_create_new_order only: { path: /api/v1/agent/swap/quote, body: { requoteOf: <the order's quoteId> } | null }.
    - `waited` Waited: Present when waitSec > 0.
      - `requestedSec` integer
      - `elapsedMs` integer
      - `changed` boolean
      - `limited` boolean: true: answered early because too many long-polls were open or the shared provider budget was spent.
      - `limitedReason` 'concurrency' | 'upstream_budget'
      - `aborted` boolean: The client went away during the wait.
    - `afterSwap` object: Only on a completed order: where an optional rating can be sent. Nothing else.
      - `feedback` object
        - `optional` true
        - `tool` 'submit_feedback': The MCP tool.
        - `path` string
        - `arguments` object
          - `orderId` string
          - `rating` unknown

## Other responses

- `400`: invalid_request: waitSec not an integer from 0 to 25 (details.reason invalid_format | out_of_range) or sinceState not an agent state (invalid_state).
- `403`: verification_failed: verify does not match the order's destination address.
- `404`: not_found: unknown orderId.
- `429`: Rate limited. Honor Retry-After.
- `502`: upstream_error or upstream_invalid while reading the provider status. details.lastKnown {status, state, observedAt} is the last state seen, never served as current. The order is unaffected: do not create another order or send again; retry later.
- `503`: upstream_unavailable or upstream_unreachable (retryable), with details.lastKnown as for 502.
- `504`: upstream_timeout (retryable), with details.lastKnown as for 502.

---

[API](https://skmtc.dev/xmrtousdc/apis/swapper-agent-api.md) · [All operations](https://skmtc.dev/xmrtousdc/apis/swapper-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/xmrtousdc/apis/swapper-agent-api/revisions/9f4252126907?raw)
