> For the complete documentation index, see [llms.txt](https://tri.gitbook.io/tri-doc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tri.gitbook.io/tri-doc/liquidity-pool/purchase-assets.md).

# Purchase Assets

### Liquidity Pool

<mark style="color:green;">`POST`</mark> <https://sandbox.triangle.digital/api/v2/liquidity\\_pool/clients/{client\\_id}/assets/purchase\\_assets>

Buys the carbon pool quantities of the given invoices. Each line is paid by the employee of its invoice. Amounts are objects with an ISO 4217 currency and a decimal-string amount; dates are ISO 8601 in UTC.

**What a line reports.** A line is filled from what actually happened to it:

* `asset_name`, `asset_id`, `viewer`, `instrument_type`, `carbon_type` (Expense Type), `co2`, `pool_px`, `notional` and the invoice fields — as soon as the line has a price quoted from the pools;
* the payer — `employee_id`, `client_employee_id`, `employee_name`, `individual_consumption`, and `credit_card` (whether that payer has a card on file);
* `transaction_id`, `payment_intent_id`, `payment_source`, `credit_card_last4` and `created_at` — only on a line that was charged;
* `retirement_id` and `delivered_co2` — only on a line that was delivered;
* `status` and `comment` — on every line, charged or not.

So a line that was charged but not delivered reports its payment in full and leaves the delivery empty, and a line that was never charged still reports its price and its payer. `pool_px` and `notional` are `null` only when no price could be quoted for the line — the `insufficient_qty` case, where the pools had nothing left to cover it.

**Adding up what was paid.** Take `data.notional`, the amount actually charged for the whole purchase; `notional` is present on lines that were never charged too, so summing the lines overstates it. Each line is paid by itself, so `payment_intent_id` is the identifier of that line's own payment and differs from line to line.

**Path parameters**

<table data-full-width="true" data-search="false"><thead><tr><th width="230">Name</th><th width="110">Type</th><th>Description</th><th width="210">Where to take it from</th></tr></thead><tbody><tr><td><code>client_id</code></td><td>integer</td><td><strong>Required.</strong> The client whose invoices are purchased.</td><td><a href="/tri-doc/liquidity-pool/get-clients.md">Get Clients</a> — <code>client_id</code></td></tr></tbody></table>

**Headers**

<table data-full-width="true"><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td>Content-Type</td><td><code>application/json</code></td></tr><tr><td>Idempotency-Key</td><td><strong>Required when <code>payment_source</code> is <code>balance</code>.</strong> Value unique to this attempt, up to 48 characters; repeating it does not pay twice.</td></tr></tbody></table>

**Body**

<table data-full-width="true" data-search="false"><thead><tr><th width="200">Name</th><th width="110">Type</th><th>Description</th><th width="260">Example</th></tr></thead><tbody><tr><td><code>hash</code></td><td>string</td><td><strong>Required.</strong> API key.</td><td>fk5f0iuy-rr06-j4x3-i75b-fy2s67s4ilo1</td></tr><tr><td><code>invoice_ids</code></td><td>array</td><td><strong>Required.</strong> Invoices to purchase; duplicates are refused. From <a href="/tri-doc/liquidity-pool/get-invoices.md">Get Invoices</a> — <code>invoice_id</code>.</td><td>[61]</td></tr><tr><td><code>reservation_group</code></td><td>uuid</td><td>The QTY hold this purchase spends. Send it whenever the quantity was held first: the hold is invisible to everyone else, this purchase included, so without the group the purchase competes with its own hold. A hold that belongs to another client is refused. It also keys the balance operations of this purchase, so a retry with the same group is not charged twice. From <a href="/tri-doc/liquidity-pool/reserve-invoices-qty.md">Reserve Invoices QTY</a> — <code>reservation_group</code>.</td><td>6f7d2a41-8c3e-4b90-9a55-2f0c1de7b834</td></tr><tr><td><code>payment_source</code></td><td>string</td><td>How the purchase is paid. One of: <code>card</code>, <code>balance</code>. Default: <code>card</code>. With <code>balance</code> each line is paid from the Spending Capacity balance of the employee of its invoice (see <a href="/tri-doc/liquidity-pool/balance.md">Balance</a>). When the payer has <code>autofill_balance</code> off, the total of a purchase paid from the balance must be at least 1 QTY and $25.00; a smaller one is refused with <code>below_minimum</code>. With <code>autofill_balance</code> on that minimum does not apply — the balance is topped up from their saved card in $25 units to cover the purchase. Paying by card has no minimum.</td><td>balance</td></tr></tbody></table>

```json
{
    "hash": "fk5f0iuy-rr06-j4x3-i75b-fy2s67s4ilo1",
    "invoice_ids": [61],
    "payment_source": "card",
    "reservation_group": "6f7d2a41-8c3e-4b90-9a55-2f0c1de7b834"
}
```

**Response fields**

The fields of a line are the same as in [Get Carbon Transactions](/tri-doc/liquidity-pool/get-carbon-transactions.md) and [Buy Carbon Pool](/tri-doc/liquidity-pool/buy-carbon-pool.md), plus the invoice of the line, what was delivered, the Average Price Wallet flag, the status and its comment. The Carbon Pool company fields are not part of an invoice line — such a purchase is billed to an employee. Both arrays carry the same fields.

<table data-full-width="true" data-search="false"><thead><tr><th width="400">Field</th><th width="110">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>message</code></td><td>string</td><td>Human-readable result of the call.</td></tr><tr><td><code>success</code></td><td>boolean</td><td><code>true</code> when at least part of the basket went through; <code>false</code> (HTTP 400) when the payment was not processed at all.</td></tr><tr><td><code>data.error_code</code></td><td>string</td><td>Empty when every line went through; <code>partial</code> when any line did not — <code>data.purchase_failed</code> then says which and why. On a refused call one of: <code>invalid_request</code>, <code>nothing_to_purchase</code> (the invoices carried no quantity to buy, so both arrays are empty), <code>below_minimum</code> (the purchase is under the minimum for paying from the balance with <code>autofill_balance</code> off).</td></tr><tr><td><code>data.notional</code></td><td>object</td><td>Amount actually charged — currency and decimal string. <code>0.00</code> when nothing was charged. A line that was paid but not delivered is part of it; a line that was never charged (<code>payment_failed</code>, <code>on_average_price_wallet</code>, <code>insufficient_qty</code>) is not.</td></tr><tr><td><code>data.purchase_successful[]</code></td><td>object</td><td>Lines paid and delivered — fields below, <code>status</code> is <code>paid</code>.</td></tr><tr><td><code>data.purchase_failed[]</code></td><td>object</td><td>Lines that did not go through — the same fields; <code>status</code> says why, and what the line reports follows the rule above.</td></tr><tr><td><code>data.purchase_successful[].asset_name</code></td><td>string</td><td>Asset the credits were purchased from, under its current name.</td></tr><tr><td><code>data.purchase_successful[].asset_id</code></td><td>string</td><td>Identifier of that asset — 32 uppercase hexadecimal characters. It is the stable half of the pair: <code>asset_name</code> is the asset's CURRENT name and changes when the asset is renamed, while this value does not. Known from the plan, so it is reported whatever became of the payment; <code>null</code> only on <code>insufficient_qty</code>, where the line never matched a pool record. <a href="/tri-doc/liquidity-pool/get-available-assets.md">Get Available Assets</a> answers it for the previewed line and <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> for the transaction afterwards.</td></tr><tr><td><code>data.purchase_successful[].viewer</code></td><td>string</td><td>Address of the asset's Parameters Viewer — the page holding the parameters of that asset. Known from the plan like <code>asset_id</code>, so it is reported whatever became of the payment. <code>null</code> when there is no such page: only a Carbon Credit asset has one, and an <code>insufficient_qty</code> line has no asset at all.</td></tr><tr><td><code>data.purchase_successful[].instrument_type</code></td><td>string</td><td>Instrument Type of that asset. Known from the plan like <code>asset_id</code>, so it is reported whatever became of the payment; <code>null</code> only on <code>insufficient_qty</code>, and when the asset carries none. An asset may carry several; they come back in one string, comma-separated — for example <code>Engineered, Nature-based</code>.</td></tr><tr><td><code>data.purchase_successful[].transaction_id</code></td><td>uuid</td><td>Identifier of the transaction the line became. Read it back with <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> — <code>transaction_id</code>. <code>null</code> on a line that was never charged: there is no transaction to read back.</td></tr><tr><td><code>data.purchase_successful[].retirement_id</code></td><td>string</td><td>Hash of the Ethereum transaction that retired the purchased quantity. <code>null</code> when the quantity was not retired on Ethereum, and on any line that was not delivered.</td></tr><tr><td><code>data.purchase_successful[].payment_intent_id</code></td><td>string</td><td>Identifier of the payment at the provider. Each line is paid by itself, so this value belongs to that line alone; <code>null</code> when paid from the balance or when the line was never charged.</td></tr><tr><td><code>data.purchase_successful[].employee_id</code></td><td>integer</td><td>Employee who pays the line, as in <a href="/tri-doc/liquidity-pool/get-employees.md">Get Employees</a> — <code>employee_id</code>; <code>null</code> on an Individual Consumption line.</td></tr><tr><td><code>data.purchase_successful[].client_employee_id</code></td><td>string</td><td>Client-facing Employee ID (the <code>employee_number</code> of <a href="/tri-doc/liquidity-pool/get-employees.md">Get Employees</a>); <code>null</code> on an Individual Consumption line.</td></tr><tr><td><code>data.purchase_successful[].employee_name</code></td><td>string</td><td>Name of the employee who pays the line. The payer is known from the invoice, so it is reported whatever became of the payment; unlike <code>employee_id</code> it is filled on an Individual Consumption line as well, where it repeats the name inside <code>individual_consumption</code>.</td></tr><tr><td><code>data.purchase_successful[].individual_consumption</code></td><td>object</td><td>Individual Consumption billed — <code>individual_consumption_id</code> (as in <a href="/tri-doc/liquidity-pool/get-individual-consumptions.md">Get Individual Consumptions</a>), <code>name</code>, <code>email</code>; <code>null</code> on other lines and on clients that are not Business accounts.</td></tr><tr><td><code>data.purchase_successful[].credit_card</code></td><td>boolean</td><td>Whether the payer of the line has a credit card on file. It describes the payer, not this payment, so it is reported whatever the line was paid with; the card that was actually charged is <code>credit_card_last4</code>.</td></tr><tr><td><code>data.purchase_successful[].credit_card_last4</code></td><td>string</td><td>Last 4 digits of the card that was charged; <code>null</code> when paid from the balance or when the line was never charged.</td></tr><tr><td><code>data.purchase_successful[].payment_source</code></td><td>string</td><td>How the line was paid: <code>balance</code> — from the Spending Capacity balance, <code>card</code> — by credit card. <code>null</code> on a line that was never charged.</td></tr><tr><td><code>data.purchase_successful[].carbon_type</code></td><td>string</td><td>Expense Type. One of: AI Compute, Air Travel, Vehicle, Lodging or Food.</td></tr><tr><td><code>data.purchase_successful[].co2</code></td><td>number</td><td>Quantity the line was priced for, tons.</td></tr><tr><td><code>data.purchase_successful[].pool_px</code></td><td>object</td><td>Price per ton the line was quoted at — currency and decimal string. Reported even when the line was never charged; <code>null</code> only on <code>insufficient_qty</code>.</td></tr><tr><td><code>data.purchase_successful[].notional</code></td><td>object</td><td>Line total at that price — currency and decimal string. Same rule as <code>pool_px</code>, so it is not by itself proof that the money moved — see <strong>Adding up what was paid</strong> above.</td></tr><tr><td><code>data.purchase_successful[].created_at</code></td><td>string</td><td>When the purchase happened, ISO 8601 in UTC; <code>null</code> on a line that was never charged.</td></tr><tr><td><code>data.purchase_successful[].invoice_id</code> / <code>invoice_number</code></td><td>integer / string</td><td>The invoice of the line, as in <a href="/tri-doc/liquidity-pool/get-invoices.md">Get Invoices</a>.</td></tr><tr><td><code>data.purchase_successful[].transaction_type</code></td><td>string</td><td>Transaction Type of the expense lines this line covers, when there is exactly one — the category the purchased CO2 is attributed to. One of <code>business_travel</code>, <code>employee_commuting</code>, <code>purchased_goods_and_services</code>, as in <a href="/tri-doc/liquidity-pool/get-invoices.md">Get Invoices</a> — <code>expense_lines[].transaction_type</code>. <code>null</code> when those lines carry different types (see <code>transaction_types</code>) or carry no Transaction Type. <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> answers the same value for the transaction this line became.</td></tr><tr><td><code>data.purchase_successful[].transaction_types</code></td><td>array</td><td>Every Transaction Type of the expense lines the line covers, each once. <code>[]</code> when they carry none. <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> answers the same list for the transaction this line became.</td></tr><tr><td><code>data.purchase_successful[].delivered_co2</code></td><td>number</td><td>Quantity actually retired; lower than <code>co2</code> when the pool record had less left, and <code>0</code> when the line was not delivered. Paid from the balance, the difference is returned automatically and appears in the journal as a refund.</td></tr><tr><td><code>data.purchase_successful[].average_price_wallet</code></td><td>boolean</td><td>Whether the line sits on the Average Price Wallet.</td></tr><tr><td><code>data.purchase_successful[].status</code></td><td>string</td><td>Outcome of the line — see <strong>Status values</strong> below.</td></tr><tr><td><code>data.purchase_successful[].comment</code></td><td>string</td><td>The same outcome in words, and the one place that says what happened to the money and to the tokens. <code>paid</code>: <code>Paid and burned.</code> <code>not_delivered</code>: <code>Paid, but the token burn was not completed: "&#x3C;reason>". The money was returned to the balance.</code> — the reason is quoted as the network worded it (left out when it gave none), and paid by card the last sentence is <code>The money was not returned - please contact Triangle Admin.</code> A line that was never charged says so and names why: <code>payment_failed</code> — <code>The payment was not processed - check the payment method and try again. No payment was taken and no tokens were burned.</code>, or, when the same invoices are submitted twice within 10 seconds, <code>The same purchase was submitted less than 10 seconds ago. No payment was taken and no tokens were burned.</code>; <code>on_average_price_wallet</code> — <code>Purchase QTY is on Average Price Wallet, please contact Triangle Admin. No payment was taken and no tokens were burned.</code>; <code>insufficient_qty</code> — <code>The pool does not have enough quantity to cover this line. No payment was taken and no tokens were burned.</code> For a charged line the same text is answered by <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> afterwards.</td></tr><tr><td><code>operations_count</code></td><td>integer</td><td>Always <code>1</code>.</td></tr></tbody></table>

**Status values**

<table data-full-width="true" data-search="false"><thead><tr><th width="250">status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>paid</code></td><td>The line was paid and delivered. It appears in <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> with <code>status</code> <code>approved</code>.</td></tr><tr><td><code>not_delivered</code></td><td>The line was paid, but the quantity could not be retired on the network. The retirement is rolled back, so the invoice stays open for that quantity and <code>delivered_co2</code> is <code>0</code>. Paid from the balance, the money is returned automatically; paid by card, contact Triangle support. <code>comment</code> carries the reason the network gave. It appears in <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> with <code>status</code> <code>failed</code>, and its <code>transaction_id</code> is filled, so it can be read back there directly.</td></tr><tr><td><code>payment_failed</code></td><td>The payment of the line was refused; nothing was charged, nothing was burned and nothing was delivered. The line still reports the price it was quoted at.</td></tr><tr><td><code>on_average_price_wallet</code></td><td>The line sits on the Average Price Wallet and cannot be bought here; nothing was charged for it and nothing was burned — contact Triangle support. The line still reports the price it was quoted at.</td></tr><tr><td><code>insufficient_qty</code></td><td>The pools had no quantity left for the line; nothing was charged for it and nothing was burned. This is the one line with no asset and no price to report, so <code>asset_name</code>, <code>asset_id</code>, <code>viewer</code>, <code>instrument_type</code>, <code>pool_px</code> and <code>notional</code> are <code>null</code>.</td></tr></tbody></table>

**Response**

{% tabs %}
{% tab title="200" %}
One invoice, two expense types: the Vehicle line was paid and delivered, the Air Travel line sits on the Average Price Wallet and was never charged — it still reports its price and its payer, and only the Vehicle line is counted in `data.notional`. A card payment; paid from the balance, `payment_source` is `balance` and `payment_intent_id` and `credit_card_last4` are `null`.

{% code fullWidth="false" %}

```json
{
    "message": "Part of the purchase went through. Purchase QTY is on Average Price Wallet, please contact Triangle Admin.",
    "success": true,
    "data": {
        "error_code": "partial",
        "notional": { "currency": "USD", "amount": "18.50" },
        "purchase_successful": [
            {
                "asset_name": "Walker Ranch UAT 2",
                "asset_id": "D689021E04987FB9476852972D0AE958",
                "viewer": "https://sandbox.triangle.digital/v/16924",
                "instrument_type": "Nature-based",
                "transaction_id": "dffdf0b3-3158-41c9-9795-f026bbc6c233",
                "retirement_id": "0xfa26fd5dd02b9c1f0a5f5c7f0e2c1a3d4b5e6f708192a3b4c5d6e7f8091a2b3c",
                "payment_intent_id": "pi_3U13HLIqyi8MGgBI2lkvamAs",
                "employee_id": 18,
                "client_employee_id": "D2354324",
                "employee_name": "John Smith",
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": "4242",
                "payment_source": "card",
                "carbon_type": "Vehicle",
                "co2": 1.0,
                "pool_px": { "currency": "USD", "amount": "18.50" },
                "notional": { "currency": "USD", "amount": "18.50" },
                "created_at": "2026-08-05T11:46:50Z",
                "invoice_id": 61,
                "invoice_number": "56WFFS23",
                "transaction_type": "business_travel",
                "transaction_types": ["business_travel"],
                "delivered_co2": 1.0,
                "average_price_wallet": false,
                "status": "paid",
                "comment": "Paid and burned."
            }
        ],
        "purchase_failed": [
            {
                "asset_name": "Climate Capital Asset",
                "asset_id": "4B81D6CA57F2490E8AD3C1F70B95E2D6",
                "viewer": "https://sandbox.triangle.digital/v/16938",
                "instrument_type": "Engineered, Nature-based",
                "transaction_id": null,
                "retirement_id": null,
                "payment_intent_id": null,
                "employee_id": 18,
                "client_employee_id": "D2354324",
                "employee_name": "John Smith",
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": null,
                "payment_source": null,
                "carbon_type": "Air Travel",
                "co2": 1.0,
                "pool_px": { "currency": "USD", "amount": "12.12" },
                "notional": { "currency": "USD", "amount": "12.12" },
                "created_at": null,
                "invoice_id": 61,
                "invoice_number": "56WFFS23",
                "transaction_type": "business_travel",
                "transaction_types": ["business_travel"],
                "delivered_co2": 0.0,
                "average_price_wallet": true,
                "status": "on_average_price_wallet",
                "comment": "Purchase QTY is on Average Price Wallet, please contact Triangle Admin. No payment was taken and no tokens were burned."
            }
        ]
    },
    "operations_count": 1
}
```

{% endcode %}

A line that was paid but not delivered: the payment is reported in full, `retirement_id` is `null` and `delivered_co2` is `0`. The money did leave, so the line counts towards `data.notional`. Paid by card, so it did not come back — `comment` says so, and names the reason the network gave.

{% code fullWidth="false" %}

```json
{
    "message": "The lines marked with ! were not delivered and the quantity is still on the invoice. They were paid for - please contact Triangle Admin.",
    "success": true,
    "data": {
        "error_code": "partial",
        "notional": { "currency": "USD", "amount": "18.50" },
        "purchase_successful": [],
        "purchase_failed": [
            {
                "asset_name": "Walker Ranch UAT 2",
                "asset_id": "D689021E04987FB9476852972D0AE958",
                "viewer": "https://sandbox.triangle.digital/v/16924",
                "instrument_type": "Nature-based",
                "transaction_id": "8352d05a-2ecd-40ab-b3a2-6a91f4159a7e",
                "retirement_id": null,
                "payment_intent_id": "pi_3U6woLIqyi8MGgBI1JhIeOyy",
                "employee_id": 18,
                "client_employee_id": "D2354324",
                "employee_name": "John Smith",
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": "4242",
                "payment_source": "card",
                "carbon_type": "Lodging",
                "co2": 1.0,
                "pool_px": { "currency": "USD", "amount": "18.50" },
                "notional": { "currency": "USD", "amount": "18.50" },
                "created_at": "2026-08-21T18:05:03Z",
                "invoice_id": 61,
                "invoice_number": "56WFFS23",
                "transaction_type": "business_travel",
                "transaction_types": ["business_travel"],
                "delivered_co2": 0.0,
                "average_price_wallet": false,
                "status": "not_delivered",
                "comment": "Paid, but the token burn was not completed: \"execution reverted: Insufficient balance\". The money was not returned - please contact Triangle Admin."
            }
        ]
    },
    "operations_count": 1
}
```

{% endcode %}
{% endtab %}

{% tab title="400" %}
The request is not valid or `invoice_ids` contains duplicates; nothing was charged and nothing was purchased. `data.error_code` is `invalid_request` and the message says what exactly is wrong.

```json
{
    "message": "invoice_ids contains duplicates.",
    "success": false,
    "data": {
        "error_code": "invalid_request"
    },
    "operations_count": 1
}
```

The invoices were read but carried no quantity to buy — already purchased, or never had any. Nothing was charged, and there is no line to report either way: both arrays are empty and `data.error_code` is `nothing_to_purchase`.

```json
{
    "message": "Nothing to purchase.",
    "success": false,
    "data": {
        "error_code": "nothing_to_purchase",
        "notional": { "currency": "USD", "amount": "0.00" },
        "purchase_successful": [],
        "purchase_failed": []
    },
    "operations_count": 1
}
```

An invalid API key is refused before the request is read, so that answer carries no `data`.

```json
{
    "message": "Error! No such hash.",
    "success": false
}
```

{% endtab %}
{% endtabs %}
