> 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/buy-carbon-pool.md).

# Buy Carbon Pool

### Liquidity Pool

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

Buys the selected carbon pool records and answers with the purchased lines, one entry per requested expense type. 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:

* `record_id`, `asset_name`, `asset_id`, `viewer`, `instrument_type`, `carbon_type` (Expense Type), `co2`, `pool_px` and `notional` — as soon as the line has a price quoted from the pools;
* the payer — `employee_id`, `client_employee_id`, `employee_name`, `individual_consumption`, the Carbon Pool company fields, 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` — only on a line that was delivered;
* `status` and `comment` — on every line the purchase reached an outcome for, charged or not.

So a line that was charged but not delivered reports its payment in full and leaves only `retirement_id` empty, and a line that was never charged — an Average Price Wallet record, or any line of a refused purchase — still reports the price it was quoted at. `pool_px` and `notional` are `null` only when no price could be quoted for the line: a request refused as `invalid_request`, or a quantity the pools no longer have (`pool_changed`).

**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. One payment covers the whole purchase, and its identifier is reported on every line that was charged — `payment_intent_id`.

**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 the purchase is made for.</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. A repeat of an already processed purchase is refused with <code>duplicate_operation</code> — nothing is paid or delivered 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>pools</code></td><td>array</td><td><strong>Required.</strong> The records to buy, one entry per expense type: <code>record_id</code> and <code>carbon_type</code> (Expense Type), one of AI Compute, Air Travel, Vehicle, Lodging or Food, come from <a href="/tri-doc/liquidity-pool/get-pool-list.md">Get Pool List</a>; <code>qty</code> is a number greater than 0. Duplicate <code>record_id</code>/<code>carbon_type</code> pairs are refused.</td><td><pre class="language-json"><code class="lang-json">[
  { "record_id": 5115, "carbon_type": "Air Travel", "qty": 1.0 },
  { "record_id": 5241, "carbon_type": "Vehicle", "qty": 4.0 }
]
</code></pre></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 and can be refused with <code>pool_changed</code>. A hold that belongs to another client is refused. From <a href="/tri-doc/liquidity-pool/reserve-pool-price-qty.md">Reserve Pool Price QTY</a> — <code>reservation_group</code>.</td><td>6f7d2a41-8c3e-4b90-9a55-2f0c1de7b834</td></tr><tr><td><code>payer_type</code></td><td>string</td><td>Who pays. One of: <code>account</code>, <code>employee</code> (non-Business); <code>company</code>, <code>individual_consumption</code> (Business). Default for non-Business: <code>account</code>.</td><td>company</td></tr><tr><td><code>company_id</code></td><td>uuid</td><td>Required when <code>payer_type</code> is <code>company</code> — together with <code>employee_id</code> of an employee of that company, who is billed. From <a href="/tri-doc/liquidity-pool/get-companies.md">Get Companies</a> — <code>company_id</code>.</td><td>dcd1e0a0-ed57-4180-846f-efbe5ea42828</td></tr><tr><td><code>individual_consumption_id</code></td><td>uuid</td><td>Required when <code>payer_type</code> is <code>individual_consumption</code>. From <a href="/tri-doc/liquidity-pool/get-individual-consumptions.md">Get Individual Consumptions</a> — <code>individual_consumption_id</code>.</td><td>68117898-4278-499f-b96b-78173f3d2040</td></tr><tr><td><code>employee_id</code></td><td>integer</td><td>Required when <code>payer_type</code> is <code>employee</code> or <code>company</code> (for <code>company</code> — an employee of that company). From <a href="/tri-doc/liquidity-pool/get-employees.md">Get Employees</a> — <code>employee_id</code>.</td><td>345</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> the payer named by <code>payer_type</code> pays from their Spending Capacity balance (see <a href="/tri-doc/liquidity-pool/balance.md">Balance</a>) and does not need a saved card. 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",
    "payer_type": "company",
    "company_id": "dcd1e0a0-ed57-4180-846f-efbe5ea42828",
    "employee_id": 345,
    "payment_source": "card",
    "reservation_group": "6f7d2a41-8c3e-4b90-9a55-2f0c1de7b834",
    "pools": [
        { "record_id": 5115, "carbon_type": "Air Travel", "qty": 1.0 },
        { "record_id": 5241, "carbon_type": "Vehicle", "qty": 4.0 }
    ]
}
```

**Response fields**

The fields of a line are the same as in [Get Carbon Transactions](/tri-doc/liquidity-pool/get-carbon-transactions.md) and [Purchase Assets](/tri-doc/liquidity-pool/purchase-assets.md), plus `record_id`, `status` and `comment`.

<table data-full-width="true" data-search="false"><thead><tr><th width="360">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>Whether the call succeeded; a refused call answers HTTP 400.</td></tr><tr><td><code>data.error_code</code></td><td>string</td><td>Empty when everything was sold; <code>partial</code> when part of the basket was not. On a refused call one of: <code>invalid_request</code>, <code>pool_changed</code>, <code>no_card</code>, <code>amount_too_small</code>, <code>insufficient_balance</code>, <code>balance_error</code>, <code>card_declined</code>, <code>duplicate_operation</code> (the Idempotency-Key was already used by a processed purchase), <code>duplicate_purchase</code> (the same purchase — same pools, payer and payment source — was submitted again less than 10 seconds ago), <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.</td></tr><tr><td><code>data.pools[].record_id</code></td><td>integer</td><td>The requested record, as passed in the body.</td></tr><tr><td><code>data.pools[].status</code></td><td>string</td><td>Outcome of the line — see <strong>Status values</strong> below. What the line fills follows the rule at the top of this page.</td></tr><tr><td><code>data.pools[].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>ok</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> <code>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>null</code> when <code>status</code> is <code>null</code>: the purchase was refused before any line had an outcome, and the reason is in <code>message</code> and <code>data.error_code</code>. The same text is answered by <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> for the line afterwards.</td></tr><tr><td><code>data.pools[].asset_name</code></td><td>string</td><td>Asset the credits were purchased from, under its current name; filled whatever the outcome of the line.</td></tr><tr><td><code>data.pools[].asset_id</code></td><td>string</td><td>Identifier of that asset — 32 uppercase hexadecimal characters — resolved from the pool record, so it is filled whatever the outcome of the line: a line that was never charged still says which asset it was about. 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. <a href="/tri-doc/liquidity-pool/get-pool-list.md">Get Pool List</a> answers it for the record before the purchase, and <a href="/tri-doc/liquidity-pool/get-carbon-transactions.md">Get Carbon Transactions</a> after it.</td></tr><tr><td><code>data.pools[].viewer</code></td><td>string</td><td>Address of the asset's Parameters Viewer — the page holding the parameters of that asset. Resolved from the pool record like <code>asset_id</code>, so it is filled whatever the outcome of the line. <code>null</code> when the asset has no such page: only a Carbon Credit asset has one.</td></tr><tr><td><code>data.pools[].instrument_type</code></td><td>string</td><td>Instrument Type of that asset, resolved from the pool record like <code>asset_id</code>, so it is filled whatever the outcome of the line. An asset may carry several; they come back in one string, comma-separated — for example <code>Engineered, Nature-based</code>. <code>null</code> when the asset carries none.</td></tr><tr><td><code>data.pools[].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.pools[].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.pools[].payment_intent_id</code></td><td>string</td><td>Identifier of the payment at the provider; one payment covers the whole purchase, so every charged line of the answer carries the same value. <code>null</code> when paid from the balance or when the line was never charged.</td></tr><tr><td><code>data.pools[].company_id</code></td><td>uuid</td><td>Carbon Pool company the purchase was made for (Business), as in <a href="/tri-doc/liquidity-pool/get-companies.md">Get Companies</a> — <code>company_id</code>; <code>null</code> otherwise.</td></tr><tr><td><code>data.pools[].client_company_id</code></td><td>string</td><td>Company ID entered by the client; <code>null</code> when the purchase is not for a company.</td></tr><tr><td><code>data.pools[].company_name</code></td><td>string</td><td>Name of that Carbon Pool company, as in <a href="/tri-doc/liquidity-pool/get-companies.md">Get Companies</a> — <code>company_name</code>. <code>null</code> when the purchase is not for a company, when the company was created without a name, and on a line that was never charged.</td></tr><tr><td><code>data.pools[].employee_id</code></td><td>integer</td><td>Employee the purchase is billed to, 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 purchase.</td></tr><tr><td><code>data.pools[].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 purchase.</td></tr><tr><td><code>data.pools[].employee_name</code></td><td>string</td><td>Name of the employee the purchase is billed to. Unlike <code>employee_id</code> it is filled on an Individual Consumption purchase as well, where it repeats the name inside <code>individual_consumption</code>. <code>null</code> on a line that was never charged.</td></tr><tr><td><code>data.pools[].individual_consumption</code></td><td>object</td><td>Individual Consumption billed — <code>individual_consumption_id</code>, <code>name</code>, <code>email</code>; <code>null</code> otherwise.</td></tr><tr><td><code>data.pools[].credit_card</code></td><td>boolean</td><td>Whether the payer has a credit card on file. It describes the payer, not this payment, so it is reported whatever the purchase was paid with; the card that was actually charged is <code>credit_card_last4</code>.</td></tr><tr><td><code>data.pools[].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.pools[].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.pools[].carbon_type</code></td><td>string</td><td>The requested Expense Type. One of: AI Compute, Air Travel, Vehicle, Lodging or Food.</td></tr><tr><td><code>data.pools[].co2</code></td><td>number</td><td>Purchased quantity, tons of CO2e; echoes the requested <code>qty</code> on a line that was not sold.</td></tr><tr><td><code>data.pools[].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.</td></tr><tr><td><code>data.pools[].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.pools[].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>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>ok</code></td><td>The line was sold and delivered; its fields are filled. 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 and nothing is taken from the pool record. 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>average_price_wallet</code></td><td>The record sits on the Average Price Wallet and cannot be bought here; nothing was charged for it and nothing was burned. The line still reports the price it was quoted at.</td></tr><tr><td><code>null</code></td><td>Nothing was charged for this line — the purchase was refused, and <code>comment</code> is <code>null</code> too: the reason is in <code>message</code> and <code>data.error_code</code>. The line reports the price it was quoted at whenever it has one.</td></tr></tbody></table>

**Response**

{% tabs %}
{% tab title="200" %}
Two records in one basket: the Vehicle line was sold and delivered, the Air Travel record sits on the Average Price Wallet and was never charged — it still reports its price. Paid from the balance, `payment_intent_id` is `null`; when the balance does not cover the purchase nothing is bought and `error_code` is `insufficient_balance`.

{% code fullWidth="false" %}

```json
{
    "message": "Part of the purchase went through; the rest could not be sold here. Purchase QTY is on Average Price Wallet, please contact Triangle Admin.",
    "success": true,
    "data": {
        "error_code": "partial",
        "notional": { "currency": "USD", "amount": "9.25" },
        "pools": [
            {
                "record_id": 5241,
                "status": "ok",
                "comment": "Paid and burned.",
                "asset_name": "Walker Ranch UAT 2",
                "asset_id": "D689021E04987FB9476852972D0AE958",
                "viewer": "https://sandbox.triangle.digital/v/16924",
                "instrument_type": "Nature-based",
                "transaction_id": "fd773f2d-c061-4683-af6e-035a79eb735f",
                "retirement_id": "0xfa26fd5dd02b9c1f0a5f5c7f0e2c1a3d4b5e6f708192a3b4c5d6e7f8091a2b3c",
                "payment_intent_id": "pi_3U13HLIqyi8MGgBI2lkvamAs",
                "company_id": "dcd1e0a0-ed57-4180-846f-efbe5ea42828",
                "client_company_id": "456",
                "company_name": "Acme Logistics",
                "employee_id": 345,
                "client_employee_id": "345",
                "employee_name": "John Smith",
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": "4242",
                "payment_source": "card",
                "carbon_type": "Vehicle",
                "co2": 0.5,
                "pool_px": { "currency": "USD", "amount": "18.50" },
                "notional": { "currency": "USD", "amount": "9.25" },
                "created_at": "2026-08-05T11:46:50Z"
            },
            {
                "record_id": 5115,
                "status": "average_price_wallet",
                "comment": "Purchase QTY is on Average Price Wallet, please contact Triangle Admin. No payment was taken and no tokens were burned.",
                "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,
                "company_id": null,
                "client_company_id": null,
                "company_name": null,
                "employee_id": null,
                "client_employee_id": null,
                "employee_name": null,
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": null,
                "payment_source": null,
                "carbon_type": "Air Travel",
                "co2": 0.5,
                "pool_px": { "currency": "USD", "amount": "12.12" },
                "notional": { "currency": "USD", "amount": "6.06" },
                "created_at": null
            }
        ]
    },
    "operations_count": 1
}
```

{% endcode %}

A line that was paid but not delivered: the payment is reported in full and only `retirement_id` stays empty. Paid from the balance, so the money is already back — `comment` says so, and names the reason the network gave.

{% code fullWidth="false" %}

```json
{
    "message": "Part of the purchase went through. The lines marked with ! were not delivered - the money for them was returned to the balance and the quantity is still available in the pool.",
    "success": true,
    "data": {
        "error_code": "partial",
        "notional": { "currency": "USD", "amount": "9.25" },
        "pools": [
            {
                "record_id": 5241,
                "status": "not_delivered",
                "comment": "Paid, but the token burn was not completed: \"execution reverted: Insufficient balance\". The money was returned to the balance.",
                "asset_name": "Walker Ranch UAT 2",
                "asset_id": "D689021E04987FB9476852972D0AE958",
                "viewer": "https://sandbox.triangle.digital/v/16924",
                "instrument_type": "Nature-based",
                "transaction_id": "e973e132-628d-46b7-904c-eee025875d1d",
                "retirement_id": null,
                "payment_intent_id": null,
                "company_id": null,
                "client_company_id": null,
                "company_name": null,
                "employee_id": null,
                "client_employee_id": null,
                "employee_name": null,
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": null,
                "payment_source": "balance",
                "carbon_type": "Food",
                "co2": 0.5,
                "pool_px": { "currency": "USD", "amount": "18.50" },
                "notional": { "currency": "USD", "amount": "9.25" },
                "created_at": "2026-08-21T18:05:03Z"
            }
        ]
    },
    "operations_count": 1
}
```

{% endcode %}
{% endtab %}

{% tab title="400" %}
The purchase was refused and nothing was charged — `data.notional` is `0.00` and `error_code` carries the code to branch on. The line still reports the price it was quoted at, so the caller can show what the purchase would have cost; `status` and `comment` are `null`, because no line reached an outcome of its own.

```json
{
    "message": "The card was declined, so nothing was purchased. Please try another card or pay from the balance.",
    "success": false,
    "data": {
        "error_code": "card_declined",
        "notional": { "currency": "USD", "amount": "0.00" },
        "pools": [
            {
                "record_id": 5241,
                "status": null,
                "comment": null,
                "asset_name": "Walker Ranch UAT 2",
                "asset_id": "D689021E04987FB9476852972D0AE958",
                "viewer": "https://sandbox.triangle.digital/v/16924",
                "instrument_type": "Nature-based",
                "transaction_id": null,
                "retirement_id": null,
                "payment_intent_id": null,
                "company_id": null,
                "client_company_id": null,
                "company_name": null,
                "employee_id": null,
                "client_employee_id": null,
                "employee_name": null,
                "individual_consumption": null,
                "credit_card": true,
                "credit_card_last4": null,
                "payment_source": null,
                "carbon_type": "Vehicle",
                "co2": 4.0,
                "pool_px": { "currency": "USD", "amount": "18.50" },
                "notional": { "currency": "USD", "amount": "74.00" },
                "created_at": null
            }
        ]
    },
    "operations_count": 1
}
```

A request refused before the purchase is planned — a malformed body, a duplicate `record_id`/`carbon_type` pair, a hold that belongs to another client — answers `invalid_request` with `data` carrying only `error_code`: there are no lines to report yet.

```json
{
    "message": "pools contains a duplicate record_id/carbon_type pair.",
    "success": false,
    "data": {
        "error_code": "invalid_request"
    },
    "operations_count": 1
}
```

{% endtab %}
{% endtabs %}
