> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getsinbad.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getsinbad.com/_mcp/server.

# Buy walkthrough

> Walk a customer through buying USDT from a merchant, from the first offer to the release.

On a BUY, the merchant's USDT is locked in escrow and the customer pays fiat outside Sinbad. The customer's wallet signs one transaction to create the escrow, and later one to mark the trade paid. This walkthrough assumes an ordinary customer wallet: it needs POL for network fees, but it needs no USDT and no token approval. See [Wallets and gas](/wallets-and-gas) for signing and fees.

## Before you start

* A customer session for the customer. See [Create a session](/sessions).
* A saved payment account for the payment method the customer will use. See [Saved payment accounts](/payment-accounts).
* A customer wallet with enough POL to pay network fees for two or three transactions.

## 1. Ask for an offer

Call [`POST /v1/offers/best`](https://docs.getsinbad.com/api-reference/offers-and-breakdowns/find-best-offer) with `side` set to `BUY`.

| Field                  | Value                                   |
| ---------------------- | --------------------------------------- |
| `tokenSymbol`          | `USDT`                                  |
| `fiatCurrencyId`       | The fiat currency the customer pays in. |
| `amount.mode`          | `FIAT` or `USDT`.                       |
| `amount.value`         | A decimal string.                       |
| `paymentMethodId`      | Required. The catalog payment method.   |
| `savedPaymentMethodId` | Optional. The customer's saved account. |

The two amount modes mean different things:

* With `FIAT`, `value` is the market value of the USDT in the selected currency, before merchant margin and fees. Sinbad converts it to USDT at the market rate, then calculates the final fiat payment, which can differ from the amount entered. The offer returns the resulting USDT quantity in `breakdownInput.amount`.
* With `USDT`, `value` is an exact token quantity.

The response holds one `instant` offer or, if nothing matches exactly, up to three `alternatives`. Each offer carries its `pricing`, display data and a `breakdownInput`.

An amount outside every listing's limits is not an error. The call returns `200` with empty results, so show a "no offer" state and let the customer change the amount. If the only match is a listing that uses the customer's own wallet, the call fails with `400 VALIDATION_ERROR` and `details.reason: "SELF_TRADE_NOT_ALLOWED"`.

Offer and breakdown requests share a rate limit per customer. See [Limits and limitations](/limits-and-limitations#rate-limits).

## 2. Create the breakdown

Call [`POST /v1/trade-breakdowns`](https://docs.getsinbad.com/api-reference/offers-and-breakdowns/create-trade-breakdown) with the chosen offer's `breakdownInput`, exactly as received, and an `Idempotency-Key` header. Send every field unchanged, including `paymentMethodId` and `savedPaymentMethodId`. A changed selection can return `400` or `409`, and a selection that no longer exists can return `404`. In either case, ask for a fresh offer.

Sinbad responds with `201`:

* `breakdownId` and `status: "AWAITING_FUNDING"`.
* `expiresAt`, 300 seconds (5 minutes) after creation.
* `tradeDetails`, with the final numbers.
* `escrowTransaction`, the prepared transaction for the customer's wallet to sign.

Show `tradeDetails` to the customer and continue only if they agree. Repeating the call with the same key and body returns the same breakdown and the same deadline.

If the merchant can no longer cover the amount, the call fails with `409 INSUFFICIENT_LIQUIDITY`. Ask for a new offer.

> **Note**
>
> Creating a breakdown also holds the customer's open trade on the BUY side. To price again, create a new breakdown with a new `Idempotency-Key`. See [One open trade per side](/offers-quotes#one-open-trade-per-side) for when that replaces the earlier breakdown and when it fails.

## 3. Sign and submit the escrow transaction

Have the customer's wallet sign `escrowTransaction`, then call [`POST /v1/transactions`](https://docs.getsinbad.com/api-reference/transactions/submit-prepared-transaction) with its `preparationId` and an `Idempotency-Key`. The customer has until `expiresAt` to do this.

```http
POST /v1/transactions
Idempotency-Key: tx-<preparationId>
Content-Type: application/json

{ "preparationId": "<escrowTransaction.preparationId>", "signedTransaction": "0x02..." }
```

If the wallet has too little POL, the call fails with `400 INSUFFICIENT_BALANCE` and `details.kind: "NATIVE_GAS"`. A `202` response means Sinbad started tracking the transaction. It does not mean the trade exists yet. For nonces, gas, retries and unknown outcomes, see [Wallets and gas](/wallets-and-gas).

## 4. Wait for the trade

The trade exists only after the escrow is confirmed on chain. Either:

* Poll [`GET /v1/transactions/{preparationId}`](https://docs.getsinbad.com/api-reference/transactions/get-prepared-transaction-status) until `transaction.status` is `CONFIRMED`, or
* Poll [`GET /v1/trade-breakdowns/{breakdownId}`](https://docs.getsinbad.com/api-reference/offers-and-breakdowns/get-trade-breakdown) until `status` is `CONFIRMED` and `tradeId` is set.

The breakdown's other statuses are `AWAITING_FUNDING`, `EXPIRED` and `REVIEW_REQUIRED`. You can also use the [`trade.created`](/webhook-events/trade-created) and [`trade.updated`](/webhook-events/trade-updated) webhooks. The new trade starts in `CREATED`.

Trade status can lag the chain, so keep polling until confirmed and reconcile with [`GET /v1/trades/{tradeId}`](https://docs.getsinbad.com/api-reference/trades/get-partner-trade). Keep polling the breakdown even if you never saw a transaction hash, because it also tells you when the breakdown has expired.

## 5. The customer pays the fiat

Read the trade with [`GET /v1/trades/{tradeId}`](https://docs.getsinbad.com/api-reference/trades/get-partner-trade). While the trade is `CREATED`, `paymentInstructions` holds the merchant's receiving account: `paymentMethodId`, `displayLabel` and a `data` object. It appears only after funding is confirmed.

The customer sends exactly `pricing.customerFiatAmount`, in the trade's fiat currency, from the saved account they chose. This payment happens outside Sinbad. The merchant may check that the sender's name matches the customer's. A mismatch is a dispute reason (`name_does_not_match_payment`).

> **Warning**
>
> Sinbad cancels an unpaid BUY trade automatically after a configured window. Ask Sinbad for the window in your environment, and do not assume the cancellation happens at an exact time.

## 6. Upload proof of payment

Upload a receipt image so the merchant can find the payment quickly. Call [`POST /v1/trades/{tradeId}/proof`](https://docs.getsinbad.com/api-reference/payment-proof/upload-partner-trade-proof) with `contentType` (`image/png` or `image/jpeg`) and the image as `dataBase64`, without a `data:` prefix. Proof does not mark the trade as paid. Limits and the other proof calls are on [Payment proof](/payment-proof).

## 7. Mark the trade as paid

After the customer has actually sent the money, mark the trade paid:

1. Call [`POST /v1/trades/{tradeId}/actions/{action}/execution`](https://docs.getsinbad.com/api-reference/trade-actions/prepare-partner-customer-action) with the `markPaid` action. The call takes no body and no `Idempotency-Key`, and every call returns a new prepared transaction.
2. Have the wallet sign the prepared transaction.
3. Submit it with [`POST /v1/transactions`](https://docs.getsinbad.com/api-reference/transactions/submit-prepared-transaction), which does need an `Idempotency-Key`.

```http
POST /v1/trades/{tradeId}/actions/markPaid/execution
-> { "transaction": { "preparationId": "...", "purpose": "markPaid", ... } }

POST /v1/transactions
Idempotency-Key: tx-<preparationId>

{ "preparationId": "<transaction.preparationId>", "signedTransaction": "0x02..." }
```

When the transaction is confirmed, the trade becomes `PAID`. Trade actions have no signing deadline, but the trade must still be `CREATED`. See [`markPaid`](/trade-actions/mark-paid) and [Trade actions](/trade-actions).

## 8. The merchant releases

The merchant checks the payment and releases the USDT. The customer signs nothing for this step. The trade becomes `RELEASED`, the customer's wallet receives `pricing.recipientAmountUsdt`, and the customer's BUY side is free for a new trade.

## What can go wrong

| Situation                                            | What to do                                                                                                                                                                             |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The customer has not paid yet and changes their mind | While the trade is `CREATED`, prepare [`cancelPrePaid`](/trade-actions/cancel-pre-paid), sign it and submit it. The trade ends `CANCELED` and the locked USDT returns to the merchant. |
| The customer paid but the merchant has not released  | After the dispute window has passed, the customer can open a dispute. See [Disputes](/disputes).                                                                                       |
| You are unsure whether an action is allowed          | `availableActions` on the trade is only a hint. Preparing an action checks the current chain state and timing.                                                                         |
| The customer needs to talk to the merchant           | Use [Chat and images](/trade-chat). It works throughout the trade.                                                                                                                     |

## Next steps

* [Disputes](/disputes) for what to do when payment and release do not match.
* [Trades and history](/trade-history) to list a customer's trades.
* [Go-live checklist](/go-live) before you send real customers to production.