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

# RFQ orders

> Buy and sell tokenized stocks through the rfq-fill route, then track each order's status to its result.

> [!WARNING]
>
> This feature is in private alpha and the details are subject to change!

The `rfq-fill` route trades Backpack Securities tokens against USDC. It works differently from the other routes:

* On the other routes, the trade completes in one transaction. A `CONFIRMED` status from `POST /v1/execute` means that the trade is done.
* On `rfq-fill`, your transaction opens an order on Solana. A solver then fills the order. A `CONFIRMED` status means that the order is open, not that it filled.

To follow the order to its result, keep sending the same `POST /v1/execute` request until the response says `done`. To look up orders later, use `GET /v1/orders`.

This recipe builds on the [swap recipe](https://docs.sunrise.xyz/recipes/swap.md). It uses the same `headers`, `wallet`, `account`, and signing code.

## Before you start

* **Eligibility.** Buying and selling tokenized stocks is only available to eligible users in supported jurisdictions. Eligibility restrictions apply under Backpack and Trek documentation and applicable law. Your use of the Sunrise API is subject to the Sunrise [Terms of Service](https://sunrise.xyz/terms-of-service), including its Backpack/Trek Tokenized Equities Disclosures.
* **Market hours.** When the stock's market is closed, `rfq-fill` gives no quote. `unavailableRoutes` lists `rfq-fill` with the code `market_closed`. Another route can still quote the stock.

## Step 1: Find a stock token

Call `GET /v1/tokens?issuer=backpack_securities` to list the Backpack Securities tokens. Use a token's `address` as its mint and its `decimals` to convert amounts.

```typescript theme={null}
const tokensResponse = await fetch(
  "https://api.sunrise.xyz/v1/tokens?issuer=backpack_securities",
  { headers },
);

const tokensResult = await tokensResponse.json();

if (!tokensResponse.ok || !tokensResult.success) {
  throw new Error(tokensResult.error?.message ?? "Could not load tokens");
}

const stock = tokensResult.data.tokens[0];
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
```

The list comes in pages. When `tokensResult.data.pagination.nextCursor` isn't `null`, call the endpoint again with `cursor` set to it, and keep `issuer` unchanged.

For a stock's ticker, exchange, and fundamentals, see [Stock data](https://docs.sunrise.xyz/recipes/stock-data.md).

## Step 2: Get a quote

`rfq-fill` quotes stock tokens against USDC only. To buy, pay with USDC. To sell, receive USDC. Choose the pair and the amount. Amounts are strings in base units.

> [tab] Buy
>
> Pay with USDC and receive the stock token. USDC has 6 decimals, so `"10000000"` means 10 USDC.
>
> ```typescript theme={null}
> const fromToken = USDC_MINT;
> const toToken = stock.address;
> const fromAmount = "10000000";
> ```

> [tab] Sell
>
> Pay with the stock token and receive USDC. Use the stock token's `decimals` for the amount.
>
> ```typescript theme={null}
> const fromToken = stock.address;
> const toToken = USDC_MINT;
> const fromAmount = String(Math.round(0.5 * 10 ** stock.decimals)); // 0.5 shares
> ```

Then call `POST /v1/quotes`, as in the [swap recipe](https://docs.sunrise.xyz/recipes/swap.md#step-3-get-a-quote):

```typescript theme={null}
const quoteResponse = await fetch("https://api.sunrise.xyz/v1/quotes", {
  method: "POST",
  headers,
  body: JSON.stringify({
    fromToken,
    toToken,
    fromAmount,
    fromAddress: account.address,
    toAddress: account.address,
  }),
});

const quoteResult = await quoteResponse.json();

if (!quoteResponse.ok || !quoteResult.success) {
  throw new Error(quoteResult.error?.message ?? "Could not get a quote");
}

const { quotes, unavailableRoutes } = quoteResult.data;
// Takes the best quote.
const quote = quotes[0];

if (!quote) {
  // Each route that gave no quote says why, in words you can show your user.
  throw new Error(unavailableRoutes[0]?.message ?? "No quote is available right now.");
}

if (!quote.unsignedTransaction) {
  throw new Error("Send both wallet addresses to get a transaction to sign");
}
```

For a stock token, the best quote can come from `rfq-fill` or from another route. Check `quote.routeName`:

* `rfq-fill`: continue with this recipe.
* Another route: the quote is a regular swap, and `CONFIRMED` means that the trade is done. Execute it as in the [swap recipe](https://docs.sunrise.xyz/recipes/swap.md), or skip it.

On a buy, the stock amount rounds down to a size that the solver can deliver. Thus, `quote.fromAmount` can be less than the USDC that you asked for. Show the quote's amounts to your user, not the requested amount.

`quote.estimatedDurationMs` estimates how long the order takes to complete after you execute it.

To add your own fee to the trade, see [Integrator fees and attribution](https://docs.sunrise.xyz/recipes/integrator-fees.md).

## Step 3: Sign the quote

Execute the quote before `quote.expiresAt`, about 10 seconds after you get it. If the quote expires, get a new quote. If you show your user a live price, get a new quote before `expiresAt` passes.

Sign the quote's transaction as in [step 4 of the swap recipe](https://docs.sunrise.xyz/recipes/swap.md#step-4-sign-execute-and-track-the-status).

> [!WARNING]
>
> **Send the signed transaction only to `POST /v1/execute`.** Sunrise sends it to Solana. Don't broadcast it yourself or through your own RPC nodes: a transaction that you broadcast yourself won't land.
>
> **The quote's transaction is a Solana v1 transaction.** Sign it with a wallet or library that supports v1 transactions, and sign it exactly as returned. A transaction that's rebuilt as a legacy or v0 transaction is rejected.

## Step 4: Execute and track the order status

Call `POST /v1/execute` with the signed transaction, `quoteId`, `routeName`, and `providerRequestId`. Send the same request again until `data.done` is `true`. The order can take a few seconds to fill, so wait between requests.

```typescript theme={null}
const execution = {
  quoteId: quote.quoteId,
  routeName: quote.routeName,
  providerRequestId: quote.providerRequestId,
  signedTransaction: getBase64Decoder().decode(signed.signedTransaction),
};

let result;

for (let attempt = 0; attempt < 300; attempt++) {
  const response = await fetch("https://api.sunrise.xyz/v1/execute", {
    method: "POST",
    headers,
    body: JSON.stringify(execution),
  }).catch(() => undefined);

  // No answer, a 5xx or a 429: send the same request again. Never sign a new quote.
  if (!response || response.status >= 500 || response.status === 429) {
    const retryAfterSeconds = Number(response?.headers.get("Retry-After"));
    const waitMs =
      Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
        ? retryAfterSeconds * 1000
        : 2000;
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    continue;
  }

  const payload = await response.json();

  if (!payload.success) {
    // A 409 means get a new quote. See the Errors page for every code.
    throw new Error(payload.error.message);
  }

  result = payload.data;
  console.log(result.status, result.orders?.[0]?.status);

  if (result.done) break;

  await new Promise((resolve) => setTimeout(resolve, 2000));
}

if (!result?.done) {
  throw new Error("The order has no final status yet. Send the same request again later");
}

const [order] = result.orders;
```

Each response has `status` and `done`:

* `SUBMITTED`: the transaction is still landing. `done` is `false`.
* `CONFIRMED`: the transaction landed and the order is open. `data.orders` lists the order. It can be missing for a moment after the transaction lands, while `done` is still `false`. `done` turns `true` once every order is final.

What happens after `CONFIRMED` depends on the side:

* **Buy.** The order holds your USDC. Your wallet doesn't hold the stock token yet. A solver fills the order shortly after, so `done` stays `false` until then.
* **Sell.** The solver pays you USDC in the same transaction that opens the order. Thus, the order is usually `FILLED` and `done` is `true` on the first `CONFIRMED` response. It can still show `OPEN` once, so keep the same loop.

An order usually reaches a final status within about `quote.estimatedDurationMs`. Show that estimate to your user. This example sends the request every 2 seconds, up to 300 times, in case the order takes longer. If the order still has no final status, it stays open. Send the same request again later.

Sending the same request again is safe: a signed transaction is executed at most once.

The solver can cancel an open order at any time. After the on-chain TTL, any wallet can cancel it. The refund always goes to your wallet. As of October 26, 2026, the TTL is 9,000 slots from when the order opens, about 30 minutes. The Sunrise API has no cancel endpoint.

## Step 5: Handle the result

Each order in `data.orders` has `orderId`, `type`, `status`, and `details`. `orderId` is the quote's `providerRequestId`. On `rfq-fill`, `type` is `rfq`. `status` decides which transaction fields `details` holds. "The input" is the token that you paid with: USDC for a buy, the stock token for a sell.

```json theme={null}
{
  "orderId": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "type": "rfq",
  "status": "FILLED",
  "details": {
    "openTxHash": "24wD2hvDYFwcvTQnBZBHmqE9sLkY66BvWMQsS61SEjXBRKhrKZuS6TrMsX1z4erNTibHSKaPpD4XBr76ff2tDCQM",
    "fillTxHash": "3Z5YjVhFzJ7bS5jDpFQ1wqfQ4rvX9x8n9RkHk9oQ6yGd7mJ9KpR6bL4tQ2uW8eN5cA3sD1fG7hJ2kL9mN4pQ6rS"
  }
}
```

| `status` | Meaning | Final | Fields in `details` |
| - | - | - | - |
| `PENDING` | The solver sent the open transaction. It has not landed. | No | `openTxHash` |
| `OPEN` | The open transaction landed. The order waits for a fill. | No | `openTxHash` |
| `FILLED` | A solver filled the order. | Yes | `openTxHash`, `fillTxHash`. Optional: `deliveredAmountRaw`, `returnedAmountRaw` |
| `PARTIALLY_FILLED` | A solver filled part of the order. The rest of the input went back to your wallet in the fill transaction. | Yes | `openTxHash`, `fillTxHash`, `returnedAmountRaw`. Optional: `deliveredAmountRaw` |
| `CANCELLED` | The order closed with no fill. The input went back to your wallet. | Yes | `openTxHash`, `cancelTxHash` |
| `REFUNDED` | The fill transaction landed but delivered nothing. The input went back to your wallet. | Yes | `openTxHash`, `refundTxHash` |
| `FAILED` | The open transaction reverted or never landed. No order exists. The input stayed in your wallet. | Yes | `openTxHash` |

The fill statuses can give two amounts as strings of raw base units:

* `deliveredAmountRaw`: the output token amount that the fill delivered to your wallet.
* `returnedAmountRaw`: the input amount that the fill returned to your wallet.

`POST /v1/execute` can show a partial fill as `FILLED`. To get the final status and amounts, use [step 6](#step-6-get-order-status-later). When an amount is not in `details`, examine `fillTxHash` in a Solana explorer.

```typescript theme={null}
switch (order.status) {
  case "FILLED":
    console.log("Filled in", order.details.fillTxHash);
    break;
  case "PARTIALLY_FILLED":
    console.log("Partially filled in", order.details.fillTxHash);
    console.log("Input returned (raw):", order.details.returnedAmountRaw);
    break;
  case "CANCELLED":
    console.log("Cancelled in", order.details.cancelTxHash);
    break;
  case "REFUNDED":
    console.log("Refunded in", order.details.refundTxHash);
    break;
  case "FAILED":
    console.log("No trade. Open transaction:", order.details.openTxHash);
    break;
}
```

For your user, `CANCELLED`, `REFUNDED`, and `FAILED` all mean the same thing: no trade happened, and the input is in their wallet. They differ only in which transaction to link to.

## Step 6: Get order status later

Two endpoints read orders after execute, in the same `{ orderId, type, status, details }` shape:

* `GET /v1/orders?walletAddress=` lists a wallet's orders, newest placed first.
* `GET /v1/orders/{txHash}` returns the orders that one transaction opened, filled, or cancelled.

Both need the same API key access as executing on `rfq-fill`. A key without it gets `403 forbidden`.

> [!NOTE]
>
> Orders show up on these endpoints about a minute after their transaction lands. Thus, they never show `PENDING` or `FAILED` orders. To follow an order that you just placed, use the [execute loop in step 4](#step-4-execute-and-track-the-order-status), which reads orders live.
>
> Orders opened before these endpoints launched aren't listed.

### List a wallet's orders

Pass the wallet as `walletAddress`. To list only one status, add `status`: `OPEN`, `FILLED`, `PARTIALLY_FILLED`, `CANCELLED`, or `REFUNDED`. A page holds up to 100 orders. When `pagination.nextCursor` isn't `null`, call the endpoint again with `cursor` set to it, and keep `walletAddress` and `status` unchanged.

```typescript theme={null}
const orders = [];
let cursor = null;

do {
  const query = new URLSearchParams({ walletAddress: account.address, status: "OPEN" });

  if (cursor) {
    query.set("cursor", cursor);
  }

  const ordersResponse = await fetch(`https://api.sunrise.xyz/v1/orders?${query}`, { headers });
  const ordersResult = await ordersResponse.json();

  if (!ordersResponse.ok || !ordersResult.success) {
    throw new Error(ordersResult.error?.message ?? "Could not load orders");
  }

  orders.push(...ordersResult.data.orders);
  cursor = ordersResult.data.pagination.nextCursor;
} while (cursor);
```

```json theme={null}
{
  "success": true,
  "data": {
    "count": 1,
    "orders": [
      {
        "orderId": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "type": "rfq",
        "status": "OPEN",
        "details": {
          "openTxHash": "24wD2hvDYFwcvTQnBZBHmqE9sLkY66BvWMQsS61SEjXBRKhrKZuS6TrMsX1z4erNTibHSKaPpD4XBr76ff2tDCQM"
        }
      }
    ],
    "pagination": { "nextCursor": null }
  },
  "timestamp": "2026-10-29T15:02:11.482Z"
}
```

### Find the orders of a transaction

Pass the hash of any of the order's transactions: its `openTxHash`, `fillTxHash`, or `cancelTxHash`. The response lists every order that the transaction touched.

```typescript theme={null}
const txResponse = await fetch(
  `https://api.sunrise.xyz/v1/orders/${order.details.openTxHash}`,
  { headers },
);

const txResult = await txResponse.json();

if (txResponse.status === 404) {
  // Not listed yet, or no order. Try again in a minute.
} else if (!txResponse.ok || !txResult.success) {
  throw new Error(txResult.error?.message ?? "Could not load the transaction's orders");
}
```

```json theme={null}
{
  "success": true,
  "data": {
    "txHash": "24wD2hvDYFwcvTQnBZBHmqE9sLkY66BvWMQsS61SEjXBRKhrKZuS6TrMsX1z4erNTibHSKaPpD4XBr76ff2tDCQM",
    "orders": [
      {
        "orderId": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "type": "rfq",
        "status": "FILLED",
        "details": {
          "openTxHash": "24wD2hvDYFwcvTQnBZBHmqE9sLkY66BvWMQsS61SEjXBRKhrKZuS6TrMsX1z4erNTibHSKaPpD4XBr76ff2tDCQM",
          "fillTxHash": "3Z5YjVhFzJ7bS5jDpFQ1wqfQ4rvX9x8n9RkHk9oQ6yGd7mJ9KpR6bL4tQ2uW8eN5cA3sD1fG7hJ2kL9mN4pQ6rS"
        }
      }
    ]
  },
  "timestamp": "2026-10-29T15:02:11.482Z"
}
```

A `404` with `not_found` means that no order is listed for the transaction. Within the first minute after it lands, that can just mean that the order isn't listed yet.

> [!NOTE]
>
> An order fills at most once. A partial fill is final: your wallet gets less than the quote's `toAmount`, and the rest of the input comes back in the same transaction. These endpoints show a partial fill as `PARTIALLY_FILLED`, with the returned input in `returnedAmountRaw`.

## Errors

`POST /v1/quotes`, `POST /v1/execute`, and the orders endpoints return the errors in [Errors](https://docs.sunrise.xyz/api-reference/errors.md). A `503` with `execute_status_unknown` means the API couldn't find out whether the transaction was sent. Send the same request again. A `502` with `bad_gateway` that repeats won't clear by itself: stop retrying and report it with the response's `requestId`.

For the full request and response schemas, see the [API specification](https://api.sunrise.xyz/openapi).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.