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

# Purchase

> Approve-and-purchase calldata that pays the downpayment and starts the loan

> [!NOTE]
>
> Seller Financing is in beta and runs on Ethereum Sepolia instances only for now (`DEVELOPMENT` instances on Ethereum). A `PRODUCTION` instance, or one on another network, answers `503 SELLER_FINANCING_UNAVAILABLE`. See [Seller Financing](/endpoints/seller-financing/introduction.md).

Builds the purchase of an `OPEN` listing for the buyer to sign: an exact approval of the downpayment on the payment token, then `purchase`. The market pulls exactly the downpayment and holds it in escrow. The loan starts at the block time of the purchase, and its interest periods count from there.

In freeze mode, the purchase freezes the listed amount in the seller's wallet. In custody mode the asset is already in the adapter, and nothing moves. Either way, the asset reaches the buyer only after full payment, through [Claim Asset](/endpoints/seller-financing/claim-asset.md).

The market admits a buyer that:

* is not the seller, the market or its adapter,
* is the designated buyer, when the listing names one,
* is verified on the market identity register, when the market names one,
* is not refused by the asset token's own identity register, because the token would otherwise refuse the delivery.

The listing must not have expired, the market must not be paused, and the seller must not hold the agent role on the asset token.

Before building, the API refuses a listing past its `expiresAt` with `409 TX_WOULD_REVERT`, error `ListingExpired` and `details.expiresAt`. [List Listings](/endpoints/seller-financing/list-listings.md) with `status=OPEN` already leaves such listings out. In freeze mode it also checks that the freeze adapter still holds its agent role and that the seller still holds the listed amount unfrozen. Either failure answers `409 TX_WOULD_REVERT`, with `AgentRoleMissing` or `InsufficientFreeBalance`.

## Path Parameters

**marketId** `string` *required*

The market's record ID, as `id` on [List Markets](/endpoints/seller-financing/list-markets.md).

**listingId** `string` *required*

The listing's ID on the market.

## Body Parameters

**from** `string` *required*

The buyer's wallet, which signs both steps. It must hold the downpayment in the payment token. Without it the answer is `400 FROM_REQUIRED`.

## Response Fields

**data** `object`

**action** `string`

`SIGN_TRANSACTIONS`.

**chainId** `integer`

The chain the steps are built for.

**steps** `array`

Any reset of an existing allowance to zero, the approval of exactly the downpayment, then `purchase`. With an allowance that already covers the downpayment, `purchase` alone, with `gasLimit`.

**confirmStepIndex** `integer`

Index of the `purchase` step.

**preflight** `object`

`{ simulated: true, gasEstimate }` when no approval is needed. With an approval step, `{ simulated: true, basis: "approvals-applied", gasEstimate, note }` when the approval and the purchase were simulated together, and the `purchase` step then carries `gasLimit`. `{ simulated: false, note }` when that bundle could not be simulated: the market's own checks of the buyer and the listing passed, and the wallet estimates gas once the approval is mined. See [Writes](/endpoints/seller-financing/introduction.md#writes).

**downpayment** `string`

The downpayment, in the payment token's smallest unit.

**downpaymentFormatted** `string`

The same in whole units.

**principal** `string`

What the buyer owes after the purchase, before interest, in smallest units.

**principalFormatted** `string`

The same in whole units.

> [!NOTE]
>
> The approval is exact, never unlimited. A wallet already holding a smaller non-zero allowance gets a step that resets it to zero first, because some tokens refuse to change a non-zero allowance.

```bash cURL theme={null}
curl -X POST "https://api.trusset.org/seller-financing/api/markets/cmsg4x7k20003sf9q1d8v6b2e/listings/4/purchase" \
  -H "X-API-Key: trusset_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"from": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8"}'
```

```typescript TypeScript theme={null}
const res = await fetch(
  `https://api.trusset.org/seller-financing/api/markets/${marketId}/listings/${listingId}/purchase`,
  {
    method: 'POST',
    headers: { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' },
    body: JSON.stringify({ from: await buyer.getAddress() })
  }
);
const { data } = await res.json();
for (const step of data.steps) {
  const tx = await buyer.sendTransaction({ to: step.to, data: step.data, value: 0, gasLimit: step.gasLimit });
  await tx.wait();
}
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "action": "SIGN_TRANSACTIONS",
    "chainId": 11155111,
    "steps": [
      {
        "to": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
        "data": "0x095ea7b3...",
        "value": "0",
        "functionName": "approve",
        "label": "Approve exactly the downpayment of 2000.0",
        "description": "Approve exactly the downpayment of 2000.0",
        "chainId": 11155111
      },
      {
        "to": "0xc35e5f74eC4C1dfB8cE2b8798D6161B706F9B5E1",
        "data": "0xefef39a1...",
        "value": "0",
        "functionName": "purchase",
        "label": "Buy listing 4 with a downpayment of 2000.0",
        "description": "Buy listing 4 with a downpayment of 2000.0",
        "gasLimit": "579315",
        "chainId": 11155111
      }
    ],
    "confirmStepIndex": 1,
    "preflight": {
      "simulated": true,
      "basis": "approvals-applied",
      "gasEstimate": "386210",
      "note": "Simulated with the approvals above applied first."
    },
    "downpayment": "2000000000",
    "downpaymentFormatted": "2000.0",
    "principal": "8000000000",
    "principalFormatted": "8000.0"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-10-19T08:58:41.507Z",
    "requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
    "instanceId": "inst_abc123"
  }
}
```

```json Error - Reserved for Another Buyer theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "TX_WOULD_REVERT",
    "message": "This listing is reserved for another buyer.",
    "details": {
      "action": "purchase",
      "error": "NotDesignatedBuyer",
      "args": [],
      "revertData": "0x49042af3"
    }
  },
  "metadata": {
    "timestamp": "2026-10-19T08:58:41.507Z",
    "requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
    "instanceId": "inst_abc123"
  }
}
```

```json Error - Listing Expired theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "TX_WOULD_REVERT",
    "message": "Listing 6 expired at 2026-10-18T12:00:00.000Z, so it can no longer be bought. Only the seller or the operator can cancel it.",
    "details": {
      "action": "purchase",
      "error": "ListingExpired",
      "expiresAt": 1792324800
    }
  },
  "metadata": {
    "timestamp": "2026-10-19T08:58:41.507Z",
    "requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
    "instanceId": "inst_abc123"
  }
}
```

```json Error - Insufficient Balance theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "The wallet 0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8 holds 1250000000 of the token, 2000000000 are needed.",
    "details": {
      "token": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
      "owner": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8",
      "balance": "1250000000",
      "needed": "2000000000"
    }
  },
  "metadata": {
    "timestamp": "2026-10-19T08:58:41.507Z",
    "requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
    "instanceId": "inst_abc123"
  }
}
```

## Error Codes

| Code | HTTP | Cause |
| - | - | - |
| `INVALID_LISTING_ID` | `400` | `listingId` is not a positive integer of up to 19 digits |
| `VALIDATION_ERROR` | `400` | `from` is malformed, or the body carries another field. `error.details` names it |
| `FROM_REQUIRED` | `400` | `from` is missing. The message names who signs: the buyer wallet that pays the downpayment |
| `MARKET_NOT_FOUND` | `404` | No market with this ID that your instance reaches on its network |
| `LISTING_NOT_FOUND` | `404` | No listing with this ID on the market |
| `INVALID_STATUS` | `409` | The listing is not `OPEN`. `details.status` names its status |
| `INSUFFICIENT_BALANCE` | `409` | `from` holds less of the payment token than the downpayment. `details` carries `token`, `owner`, `balance` and `needed` |
| `TX_WOULD_REVERT` | `409` | The purchase would revert. `details.error` names the reason, for example `ListingExpired` with `details.expiresAt`, `NotDesignatedBuyer`, `InvalidBuyer`, `IdentityNotVerified`, `BuyerNotVerified`, `SellerIsTokenAgent`, `EnforcedPause`, or in freeze mode `AgentRoleMissing` and `InsufficientFreeBalance` |
| `CHAIN_UNAVAILABLE` | `503` | The chain could not be read. Nothing was built. Retry shortly |
| `SELLER_FINANCING_UNAVAILABLE` | `503` | The instance is a `PRODUCTION` instance or not on Ethereum |
| `PURCHASE_BUILD_FAILED` | `500` | Unexpected failure. Nothing was built. Retry, or contact support with the request ID |


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