> ## Documentation Index
> Fetch the complete documentation index at: https://developer.uphold.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sell via the REST API

> Let users sell crypto to a fiat payment method — card via OCT or bank — using the Uphold REST API.

export const feature_0 = "withdraw"

A sell moves value out of the user's Uphold crypto account to a fiat external account (card via OCT or bank).

## Prerequisites

* The user has [completed onboarding](/developer-guides/user-onboarding/overview).
* The user has an internal Uphold crypto account with sufficient balance.
* **Capabilities** — card payouts require the `card-withdrawals` capability and a destination card with `octSupport: "supported"`; the crypto → fiat leg of a bank sell requires `trades`. Bank withdrawal capability varies by rail — see the per-rail guides.
* The integration uses Uphold's rails. Partner-owned rails (omnibus) follow a different flow not covered here.

## Walkthrough

<Tip>Prefer to click through the flow? See the [interactive sell walkthrough](/developer-guides/buy-and-sell/sell/interactive-sell-flow).</Tip>

***

## Select a crypto to sell

Call [List accounts](/rest-apis/core-api/accounts/list-accounts) to retrieve the user's Uphold accounts. Present crypto accounts with a non-zero balance as sellable.

```http theme={null}
GET /core/accounts
```

```json [expandable] theme={null}
{
  "accounts": [
    {
      "id": "a00507fe-628c-4f27-ae81-e1c40b2a8fb8",
      "asset": "USD",
      "balance": "0.00",
      "status": "ok"
    },
    {
      "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
      "asset": "BTC",
      "balance": "0.1",
      "status": "ok"
    },
    {
      "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19",
      "asset": "EUR",
      "balance": "0.00",
      "status": "ok"
    }
  ]
}
```

The chosen account (`f8c2d1e4` / BTC in this example) is the `origin` for the sell.

## Define the destination

The destination determines where the sell proceeds go. Choose the path that matches the user's intent:

### Fiat external account

The user receives the proceeds in a linked card or bank account.

<Warning>Apple Pay and Google Pay are not supported as sell destinations — the platforms do not expose a payout API. Card OCT is the only card-rail payout supported.</Warning>

#### Check available rails

Call [List Rails](/rest-apis/core-api/assets/list-rails) to verify the payout rail you plan to use is available. The `withdraw` feature must be present for the rail to be usable as a sell destination.

**Card:**

```http theme={null}
GET /core/rails?type=card
```

```json theme={null}
{
  "rails": [
    {
      "type": "card",
      "network": "visa",
      "method": "debit-card",
      "asset": "USD",
      "decimals": 2,
      "features": ["deposit", "withdraw"]
    }
  ]
}
```

**Bank (example: SEPA):**

```http theme={null}
GET /core/rails?type=bank&network=sepa&asset=EUR
```

```json theme={null}
{
  "rails": [
    {
      "type": "bank",
      "network": "sepa",
      "method": "bank-transfer",
      "asset": "EUR",
      "decimals": 2,
      "features": ["deposit", "withdraw"]
    }
  ]
}
```

#### Link or select the external account

Let the user link a new external account or select an existing one.

**Card:**

Call [List external accounts](/rest-apis/core-api/external-accounts/list-external-accounts) to fetch the user's saved cards.

```http theme={null}
GET /core/external-accounts
```

Make sure the selected card has `status: "ok"` and **{feature_0}** in `features`.

```json theme={null}
{
  "externalAccounts": [
    {
      "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
      "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
      "type": "card",
      "status": "ok",
      "label": "My Visa Card",
      "asset": "GBP",
      "network": "visa",
      "features": [
        "deposit",
        "withdraw"
      ],
      "details": {
        "type": "debit",
        "last4Digits": "5119",
        "expiryDate": {
          "month": 12,
          "year": 2028
        },
        "octSupport": "supported"
      }
    }
  ]
}
```

If the user wants to use a card not yet on file, call [Create external account](/rest-apis/core-api/external-accounts/create-external-account) with the card details.

```http theme={null}
POST /core/external-accounts
{
  "type": "card",
  "label": "My Visa Card",
  "number": "4921817844445119",
  "securityCode": "123",
  "expiryDate": {
    "month": 12,
    "year": 2028
  }
}
```

The response initially returns `status: "processing"` while the card is validated.

```json theme={null}
{
  "externalAccount": {
    "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
    "type": "card",
    "status": "processing",
    "label": "My Visa Card"
  }
}
```

Once validated, the status transitions to `ok` and the full details are available.

```json theme={null}
{
  "externalAccount": {
    "id": "7d5928c5-8ac4-4b0d-8b45-f332ba6a9de7",
    "ownerId": "e4ce04dc-67b7-4e9f-af91-482cb6f9fc4a",
    "type": "card",
    "status": "ok",
    "label": "My Visa Card",
    "asset": "GBP",
    "network": "visa",
    "features": [
      "deposit",
      "withdraw"
    ],
    "details": {
      "type": "debit",
      "last4Digits": "5119",
      "expiryDate": {
        "month": 12,
        "year": 2028
      },
      "octSupport": "supported"
    }
  }
}
```

<Note>Monitor the external account status via [Get external account](/rest-apis/core-api/external-accounts/get-external-account) or the `external-account.status-changed` webhook until `status` is `ok` before proceeding.</Note>

### EMD disclaimer

This disclaimer is required to comply with FCA regulations. Display it to users based in GB, the first time they link a credit/debit card or generate bank deposit details — it only needs to be shown once:

> Uphold Europe Limited is an EMD Agent of Optimus Cards UK Limited (FRN: 902034). All received funds are held in a designated safekeeping account with a regulated bank and kept separate from Uphold's own assets. These funds are not protected by the UK Financial Services Compensation Scheme.

<Info>A freshly linked card returns `"octSupport": "unknown"` while Uphold verifies OCT eligibility. Poll `GET /core/external-accounts/{id}` until `details.octSupport` is `"supported"` before using the card as a sell destination.</Info>

**Bank:**

Bank account linking varies by rail. See the per-rail guides:

* [SEPA](/developer-guides/bank-transfers/withdrawal/via-rest-api/sepa)
* [ACH](/developer-guides/bank-transfers/withdrawal/via-rest-api/ach)
* [FPS](/developer-guides/bank-transfers/withdrawal/via-rest-api/fps)
* [FedNow](/developer-guides/bank-transfers/withdrawal/via-rest-api/fednow)

Once the user has a linked bank external account, retrieve it with [List external accounts](/rest-apis/core-api/external-accounts/list-external-accounts). Filter for `type: "bank"`, `status: "ok"`, and `"withdraw"` in `features`.

```http theme={null}
GET /core/external-accounts
```

```json [expandable] theme={null}
{
  "externalAccounts": [
    {
      "id": "cc8e8abc-0f95-4b9f-8490-2569a67bd80e",
      "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604",
      "type": "bank",
      "status": "ok",
      "label": "My SEPA Bank Account",
      "asset": "EUR",
      "network": "sepa",
      "features": ["withdraw"],
      "details": {
        "bic": "EXAAAT2K",
        "iban": "AT48444441229809844"
      },
      "createdAt": "2025-03-10T09:03:45.321Z",
      "updatedAt": "2025-03-10T09:03:45.321Z"
    }
  ]
}
```

Also ensure the user has a fiat Uphold account in the bank's currency to receive the trade proceeds. If one does not exist, create it with [Create account](/rest-apis/core-api/accounts/create-account).

```http theme={null}
POST /core/accounts
{
  "label": "My EUR Account",
  "asset": "EUR"
}
```

## Transaction preview

Show the user a preview of the sell before committing. The preview is a quote — a price-locked offer valid until `expiresAt`. The amount the user specifies is `denomination.amount`; set `denomination.asset` to indicate whether the amount is expressed in the source or destination asset.

<Info>Quotes expire quickly. Capture user confirmation before `expiresAt` and re-quote if the user hesitates.</Info>

### Fiat external account

**Card (OCT):**

A card sell settles in a single transaction — the crypto account is debited, converted at the locked rate, and fiat is paid out to the card via OCT.

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f"
  },
  "destination": {
    "type": "external-account",
    "id": "aa6e6efa-8d73-497c-8278-0347f459bd68"
  },
  "denomination": {
    "asset": "USD",
    "amount": "490.00",
    "target": "destination"
  }
}
```

```json [expandable] theme={null}
{
  "quote": {
    "id": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1",
    "origin": {
      "amount": "0.0049",
      "asset": "BTC",
      "rate": "0.00001",
      "node": {
        "type": "account",
        "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "490.00",
      "asset": "USD",
      "rate": "1",
      "node": {
        "type": "external-account",
        "id": "aa6e6efa-8d73-497c-8278-0347f459bd68",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "USD",
      "amount": "490.00",
      "target": "destination",
      "rate": "0.00001"
    },
    "fees": [],
    "expiresAt": "2026-06-01T13:00:30Z"
  }
}
```

**Bank:**

A bank sell runs in two sequenced legs: a crypto-to-fiat trade lands fiat in the user's Uphold fiat account, followed by a fiat withdrawal to the linked bank account. Create a quote for each leg.

**Trade quote (crypto → fiat):**

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f"
  },
  "destination": {
    "type": "account",
    "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19"
  },
  "denomination": {
    "asset": "EUR",
    "amount": "250.00",
    "target": "destination"
  }
}
```

```json [expandable] theme={null}
{
  "quote": {
    "id": "b8c9d0e1-f2a3-4b4c-5d6e-f7a8b9c0d1e2",
    "origin": {
      "amount": "0.005",
      "asset": "BTC",
      "rate": "0.00002",
      "node": {
        "type": "account",
        "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "account",
        "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "EUR",
      "amount": "250.00",
      "target": "destination",
      "rate": "0.00002"
    },
    "fees": [],
    "expiresAt": "2026-06-01T13:05:30Z"
  }
}
```

**Withdrawal quote (fiat → bank):**

After presenting the trade quote, also create the withdrawal quote so the user sees the full sell preview before confirming. The example below uses SEPA (EUR). For rail-specific quote shapes, see the per-rail bank-transfer withdrawal guides.

```http theme={null}
POST /core/transactions/quote
{
  "origin": {
    "type": "account",
    "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19"
  },
  "destination": {
    "type": "external-account",
    "id": "cc8e8abc-0f95-4b9f-8490-2569a67bd80e"
  },
  "denomination": {
    "asset": "EUR",
    "amount": "250.00",
    "target": "origin"
  }
}
```

```json [expandable] theme={null}
{
  "quote": {
    "id": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0",
    "origin": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "account",
        "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "external-account",
        "id": "cc8e8abc-0f95-4b9f-8490-2569a67bd80e",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "EUR",
      "amount": "250.00",
      "target": "origin",
      "rate": "1"
    },
    "fees": [],
    "expiresAt": "2026-06-01T13:06:30Z"
  }
}
```

## Confirm quote

Commit the sell by calling [Create transaction](/rest-apis/core-api/transactions/create-transaction) with the quote ID.

### Fiat external account

**Card (OCT):**

No `returnUrl` or 3DS is needed — card sell transactions do not require user card authorization.

```http theme={null}
POST /core/transactions
{
  "quoteId": "a7b8c9d0-e1f2-4a3b-4c5d-e6f7a8b9c0d1"
}
```

```json [expandable] theme={null}
{
  "transaction": {
    "id": "c9d0e1f2-a3b4-4c5d-6e7f-a8b9c0d1e2f3",
    "status": "processing",
    "origin": {
      "amount": "0.0049",
      "asset": "BTC",
      "rate": "0.00001",
      "node": {
        "type": "account",
        "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "490.00",
      "asset": "USD",
      "rate": "1",
      "node": {
        "type": "external-account",
        "id": "aa6e6efa-8d73-497c-8278-0347f459bd68",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "USD",
      "amount": "490.00",
      "target": "destination",
      "rate": "0.00001"
    },
    "fees": [],
    "quotedAt": "2026-06-01T13:00:00Z",
    "createdAt": "2026-06-01T13:00:05Z",
    "updatedAt": "2026-06-01T13:00:05Z"
  }
}
```

Wait for the `core.transaction.status-changed` webhook with `status: "completed"` before notifying the user.

**Bank — trade leg (crypto → fiat):**

```http theme={null}
POST /core/transactions
{
  "quoteId": "b8c9d0e1-f2a3-4b4c-5d6e-f7a8b9c0d1e2"
}
```

```json [expandable] theme={null}
{
  "transaction": {
    "id": "d0e1f2a3-b4c5-4d6e-7f8a-b9c0d1e2f3a4",
    "status": "processing",
    "origin": {
      "amount": "0.005",
      "asset": "BTC",
      "rate": "0.00002",
      "node": {
        "type": "account",
        "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "account",
        "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "EUR",
      "amount": "250.00",
      "target": "destination",
      "rate": "0.00002"
    },
    "fees": [],
    "quotedAt": "2026-06-01T13:05:00Z",
    "createdAt": "2026-06-01T13:05:05Z",
    "updatedAt": "2026-06-01T13:05:05Z"
  }
}
```

<Note>UK retail users may encounter a `409 user_capability_failure` with `restrictions: ["financial-promotion-cooldown-running"]` for 24 hours after onboarding. See the [trade guide](/developer-guides/trade-and-send/trade/via-rest-api) for cooldown details.</Note>

Wait for the `core.transaction.status-changed` webhook with `status: "completed"` before proceeding to the withdrawal leg.

**Bank — withdrawal leg (fiat → bank):**

Once the trade settles and fiat lands in the user's Uphold fiat account, confirm the withdrawal quote.

```http theme={null}
POST /core/transactions
{
  "quoteId": "623000c8-9bdf-4a2b-aa3d-6a6b44a7f6a0"
}
```

```json [expandable] theme={null}
{
  "transaction": {
    "id": "d3e4f5a6-1b2c-4d5e-9f8a-7b6c5d4e3f2a",
    "status": "processing",
    "origin": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "account",
        "id": "c11608ff-739d-4f38-bf91-f2d51c3b9c19",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "250.00",
      "asset": "EUR",
      "rate": "1",
      "node": {
        "type": "external-account",
        "id": "cc8e8abc-0f95-4b9f-8490-2569a67bd80e",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "EUR",
      "amount": "250.00",
      "target": "origin",
      "rate": "1"
    },
    "fees": [],
    "quotedAt": "2026-06-01T13:06:00Z",
    "createdAt": "2026-06-01T13:06:05Z",
    "updatedAt": "2026-06-01T13:06:05Z"
  }
}
```

<Note>Bank withdrawals settle asynchronously — instantly for FPS / FedNow, hours to days for ACH / SEPA. See the per-rail bank-transfer withdrawal guides for timing details.</Note>

Wait for the `core.transaction.status-changed` webhook with `status: "completed"` before notifying the user.

## Monitor for settlement

Monitor each transaction in the chain by listening for `core.transaction.status-changed` webhooks or polling. Each leg fires its own event — wait for `status: "completed"` on one leg before submitting the next.

### Via webhooks

Subscribe to `core.transaction.status-changed`. The event fires on every status transition; filter on `data.transaction.status` to detect terminal states:

* `completed` — transaction settled successfully
* `failed` — transaction failed; inspect `data.transaction.errors` for details

```json theme={null}
{
  "id": "f4a5b6c7-d8e9-4f0a-1b2c-d3e4f5a6b7c8",
  "type": "core.transaction.status-changed",
  "createdAt": "2026-06-01T12:01:00Z",
  "data": {
    "transaction": {
      "id": "d4e5f6a7-b8c9-4d0e-1f2a-b3c4d5e6f7a8",
      "status": "completed"
    }
  }
}
```

### Via polling

`GET /core/transactions/{transactionId}` returns the current state. Use polling as a fallback when webhook delivery cannot be guaranteed. Terminal statuses are `completed` and `failed`.

```http theme={null}
GET /core/transactions/a0b1c2d3-e4f5-4a6b-7c8d-e9f0a1b2c3d4
```

```json theme={null}
{
  "transaction": {
    "id": "a0b1c2d3-e4f5-4a6b-7c8d-e9f0a1b2c3d4",
    "status": "completed"
  }
}
```

<Tip>Prefer webhooks over polling in production. See [Webhooks](/rest-apis/webhooks) for setup instructions.</Tip>

## Notify the user

Display a confirmation when the final `core.transaction.status-changed` webhook fires with `status: "completed"`. For card payouts, include the payout amount and currency so the user knows the card credit to expect. For bank withdrawals, confirm the amount and destination account once the withdrawal settles.

<Check>You now support crypto sell via the REST API.</Check>

## Sample transaction

A sell to a card debits the user's internal crypto account, converts at the locked quote rate, and pays out fiat to the linked card via OCT. The transaction fires a single `core.transaction.status-changed` webhook on completion.

```json [expandable] theme={null}
{
  "transaction": {
    "id": "c9d0e1f2-a3b4-4c5d-6e7f-a8b9c0d1e2f3",
    "status": "completed",
    "origin": {
      "amount": "0.0049",
      "asset": "BTC",
      "rate": "0.00001",
      "node": {
        "type": "account",
        "id": "f8c2d1e4-3a7b-4c9d-8e5f-2b1a6d3c7e8f",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "destination": {
      "amount": "490.00",
      "asset": "USD",
      "rate": "1",
      "node": {
        "type": "external-account",
        "id": "aa6e6efa-8d73-497c-8278-0347f459bd68",
        "ownerId": "cd21b26d-35d2-408a-9201-b8fdbef7a604"
      }
    },
    "denomination": {
      "asset": "USD",
      "amount": "490.00",
      "target": "destination",
      "rate": "0.00001"
    },
    "fees": [],
    "quotedAt": "2026-06-01T13:00:00Z",
    "createdAt": "2026-06-01T13:00:05Z",
    "updatedAt": "2026-06-01T13:00:30Z"
  }
}
```

## Failure handling

| Failure                                                     | What happens to funds                                                                                     | Action                                                                           |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Quote expires before user confirms                          | Quote terminates; no debit from crypto account                                                            | Re-quote and present to user                                                     |
| Insufficient crypto balance                                 | Transaction rejected at creation; no debit                                                                | Surface error to user; they must top up                                          |
| `card-withdrawals` capability missing or restricted         | Quote creation succeeds but returns non-empty `requirements`; transaction creation blocked until resolved | Prompt user to complete the outstanding KYC step, then re-quote                  |
| Destination card fails `octSupport` check                   | Transaction fails before any debit                                                                        | Re-verify `octSupport` on the card external account; relink if needed            |
| Card OCT declined by issuer at payout                       | Crypto is automatically credited back to the user's internal Uphold account                               | `core.transaction.status-changed` fires with `status: "failed"`; notify the user |
| Rate slippage — quote expired between quote and transaction | Transaction creation returns a quote-expired error; no debit                                              | Re-quote and retry                                                               |

<Info>Every terminal state — `completed` or `failed` — fires a `core.transaction.status-changed` webhook. Listen for all three to keep your UI in sync.</Info>

<Warning>If the card OCT is declined after the crypto has been debited, the crypto is returned to the user's internal account — not to an external wallet. Make sure your UI communicates this clearly.</Warning>
