Klima Ecosystem Tooling
Base Mainnet · Checking…
API v1.0.0

Klima x402 Endpoint

Co-developed by Carbonmark, this endpoint provides functionality to retire tokenized carbon credits on the Base blockchain through the Klima Protocol Retirement Aggregator straight from an AI agent or any HTTP client.

The endpoint discovers carbon liquidity, delivers live price quotes, hands back unsigned [approve, retire] calldata, executes retirement transactions, and resolves the public Carbonmark certificate once the transaction confirms.

GEThttps://x402.klimalabs.com/api/…
POSThttps://x402.klimalabs.com/api
Chain
Base mainnet (chainId=8453)
Auth
Free GET or POST; on-chain protocol fee only
Pin a version
v1.x402.klimalabs.com
x402 Discovery
Agent-Ready
Carbon Retirement on Base

Why x402

The endpoint is built for the x402 agent-payments ecosystem and plugs directly into Base MCP: an agent reads the catalog, prepares a retirement, and submits the batch through the user's Base Account wallet in a single approval. See the Base MCP setup guide for the connection steps.

All HTTP calls are free today. Use GET with query params on action paths (/discover, /quote, etc.) or POST JSON to /api with an action field — both return the same responses.

Two ways to retire:

  • Build-your-own (free GET): discoverquote prepare/retire hands back unsigned [approve, retire] calldata that you submit from a Base Account (e.g. via Base MCP). Reads are free; you pay gas and submit the batch yourself.
  • Paid relay (sign once, no gas, no Base account): prepare-auth → sign one EIP-712 token authorization → actions/retire. A Klima executor relays the retirement on-chain and pays the gas, reimbursed from your signed budget. Any third-party wallet or agent can do this — see Paid retire (relay) below.

The endpoints

Reads never move funds. Where a row lists GET or POST, POST sends {"action":"…", …params} to /api. This table is generated from the x402 manifest, so it always matches the live API.

MethodPathFeePurpose
GET / POST/discoverfreeList retirable carbon classes, credits, and reference prices.
GET / POST/quotefreeLive on-chain price for a tonnage in USDC or kVCM.
GET / POST/prepare/retirefreeUnsigned approve + retire batch for atomic submission.
POST/prepare-authfreeBuild the EIP-712 authorization to sign for a relayed retirement.
POST/actions/retirevariableRelay a signed authorization on-chain and return the certificate.
GET / POST/certificatefreeResolve Carbonmark certificate URL(s) after confirmation.
GET/api/healthfreeLiveness probe. Always 200, no payment required.
GET/.well-known/x402.jsonfreeAgent-discovery manifest for x402 directories.

1. GET or POST /discover — what's retirable

GET /discover[?carbonClass=0x...][&creditToken=0x...][&maxUsdcPricePerTonne=20]
POST /api  {"action":"discover", ...same optional filters}

Lists carbon classes and credits, each with a reference USDC/tonne price, the credits inside it (registry, vintage, token, available liquidity), plus supported input tokens and contract addresses. Filters are optional and AND-combined (maxUsdcPricePerTonne=20 means ≤ $20/tonne). chainId is not accepted here.

Reference price ≠ price at size. priceUsdcPerTonne is the marginal/spot price (accurate near 1 tonne). Large orders walk up the AAM curve — e.g. Biochar quoted ~$107.82/t at 1 t but ~$386.61/t for 100 t (≈ 58% of pool liquidity). Always call /quote for the true cost of your size.

2. GET or POST /quote — live price

GET /quote?chainId=8453&inputToken=0x...&carbonClass=0x...&amount=1.5
    [&creditToken=0x...][&vintage=2022][&tokenId=<id>]
POST /api  {"action":"quote", "chainId":8453, ...same params}

Returns the retirement price, on-chain fee, total (price + fee), suggestedMaxInput (total + 4% slippage), a humanSummary, the resolvedCredit the server selected (if no credit is specified), and alternatives.

When you don't pin a credit, the API picks the most liquid credit in the class that can cover the selected retirement amount.

{
  "tonnesFormatted": "1.5",
  "retirementPriceFormatted": "19.791336",
  "feeFormatted": "0.01",
  "totalFormatted": "19.801336",
  "suggestedMaxInputFormatted": "20.593389",
  "humanSummary": "1.5 tonnes @ 19.791336 USDC + 0.01 USDC fee = 19.801336 USDC (max 20.593389 USDC with 4% slippage)",
  "resolvedCredit": { "creditToken": "0xe662…71b8", "tokenId": 0, "vintage": 2021 }
}

3. GET or POST /prepare/retire — unsigned calldata

GET /prepare/retire?chainId=8453&inputToken=0x...&carbonClass=0x...&amount=1.5
    [&creditToken=0x...][&vintage=2022][&tokenId=<id>]
    [&maxInputTokenIn=<atomic>][&details=<urlencoded JSON>]
POST /api  {"action":"prepare/retire", "chainId":8453, ...same params}

Re-quotes on-chain and returns an ordered batch — an ERC-20 approve followed by the retirement to be submitted atomically (e.g. via Base MCP send_calls). The to field and approvalInstructions.spender are both the Settlement Contract — read them from the response rather than hard-coding.

details is an optional URL-encoded JSON object for certificate metadata. The schema is strict (unknown keys → 400):

FieldTypeMeaning
retiringAddressstringaddress performing the retirement
retiringEntityStringstringretiring-entity display name
beneficiaryAddressstringaddress credited on the certificate
beneficiaryStringstringbeneficiary display name — shows on the certificate
retirementMessagestringpublic message on the certificate
beneficiaryLocationstringToucan Puro only — required for Puro credits
consumptionCountryCodestringToucan Puro only — required for Puro credits
consumptionPeriodStartstring | numberToucan Puro only — required for Puro credits
consumptionPeriodEndstring | numberToucan Puro only — required for Puro credits

Attribution tip: beneficiaryString and retirementMessage are what make a certificate named; the certificate cannot be edited after the retirement confirms, so set them up front. (The certificate's on-chain retiringAddress reflects an internal settlement/relayer address, not the details.retiringAddress you pass.)

4. GET or POST /certificate — public proof

GET /certificate?txHash=0x...[&index=0]
POST /api  {"action":"certificate", "txHash":"0x...", ...optional index}

After the retirement confirms, resolves the shareable Carbonmark certificate URL(s) for the transaction. index selects one retirement out of a multi-retirement transaction; omit it for all.

{
  "retirementCount": 1,
  "retirements": [{
    "certificateUrl": "https://app.carbonmark.com/retirements/id/8453-0x4a7f…f4bf-0",
    "amountInTonnes": "1",
    "beneficiaryAddress": "0x1234567890123456789012345678901234567890",
    "beneficiaryName": "testing",
    "projectId": "UCR-423",
    "creditId": "UCR-423-2022"
  }]
}

beneficiaryAddress is the address credited on-chain — check this one to verify attribution. retiringAddress on the same entry is the aggregator's settlement address, not the party the retirement was made for.

A 404 retirement_not_found right after confirmation just means the subgraph hasn't indexed yet — retry in a few seconds.

Paid retire (relay) — sign once, no gas

The relay path lets any wallet or agent retire without holding native ETH, without a prior token approval, and without a Base Account. You sign one standard EIP-712 token authorization; a Klima executor submits the on-chain transaction and is reimbursed for gas out of your signed budget.

You sign the token authorization, not the retirement. The only signature is a standard EIP-3009 TransferWithAuthorization (USDC) or EIP-2612 Permit (kVCM) — exactly what any x402-style payment signs, so client signing stays plain eth_signTypedData_v4 with no custom typed data to assemble.
…and that one signature binds the whole retirement. On the USDC path the authorization's nonce is not random: it is keccak256(retirement, salt) — the exact credit, token id, amount, and attribution being authorized, plus a fresh salt the server mints per authorization. Because nonce is one of the six signed EIP-3009 fields, your signature covers what gets retired and who is credited, not just the dollar value. actions/retire rebuilds the retirement, re-hashes it, and returns 400 params_mismatch if it isn't the one you signed — so nothing between you and the endpoint can swap the retirement out after you sign.

Post actionsRetireRequest back verbatim, salt included. The kVCM (EIP-2612) path carries no commitment: Permit's nonce is the token's own counter, with no caller-chosen field to bind to.

Flow:

1. POST /api  prepare-auth   → server resolves + prices, returns:
                                 • typedData            (EIP-712 object to sign)
                                 • actionsRetireRequest (ready-to-send body,
                                     incl. `salt` on the USDC path)
2. wallet     signTypedData(typedData)        ← the ONLY signing step
3. POST /api  actions/retire (body + signature) → executor relays on-chain
4. POST /api  certificate    { txHash }       → public proof (poll if pending)

prepare-auth is the 200 alias of the 402 challenge that actions/retire returns when posted without an authPayload; either entry point gives you the same typedData. The signed budget (authValue) covers retirement + protocol fee + executor gas reimbursement, slippage-buffered. The signer needs only an input-token balance (USDC or kVCM) — no ETH.

Client SDK

The four steps are wrapped in a tiny, zero-dependency TypeScript client — sdk/klima-retire.ts. Drop the single file into your project (or npm i @klimadao/x402-retire once published — same API). It's signer-agnostic: you pass a signTypedData callback, so it works with viem, ethers, a browser wallet, or an agent's signer.

import { createClient } from "./klima-retire";
import { privateKeyToAccount } from "viem/accounts"; // or ethers, or a browser wallet

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const klima = createClient(); // https://x402.klimalabs.com, chainId 8453

const { status, transactionHash, retirements } = await klima.retire({
  from: account.address,
  amount: "1",
  carbonClass: "0xf4699531e0a5f6e9351a36de3753deaad329bf45", // from discover()
  inputToken: "usdc",                                        // or "kvcm" / an address
  details: {
    beneficiaryString: "Acme Corp",                          // shows on the certificate
    beneficiaryAddress: "0x…",                               // credited on-chain, permanent
    retirementMessage: "Net-zero 2026",
  },
  signTypedData: (td) => account.signTypedData(td as any),   // your wallet's signer
});

for (const r of retirements) console.log(r.amountInTonnes, "t →", r.certificateUrl);
Attribution is required on the relay path. Pass details.beneficiaryAddress (the party the retirement is for), or beneficiaryIsPayer: true to credit the paying wallet deliberately — omitting both returns 400 attribution_required. The beneficiary is indexed on-chain as a permanent grouping key and cannot be changed once the retirement confirms.

retire() runs the whole flow — prepare-auth → sign → submit → poll the certificate — and returns once settled (or pending_index with a transactionHash you resolve later). The same client also exposes discover(), quote(), and certificate().

actions/retire responses

statusMeaning
settledMined and indexed — retirements[] carries the certificate URL(s).
pending_indexMined (or broadcast) but subgraph not caught up — poll /certificate with transactionHash.
400 unsupported_chain_idchainId is not a supported network. Use 8453 (Base mainnet) or 84532 (Base Sepolia).
400 unsupported_input_tokeninputToken is not an accepted payment token on this chain. Use the USDC or kVCM address for the chain — see the manifest, or the addresses in the endpoint documentation.
400 invalid_input_tokeninputToken passed validation but matches neither settlement path (EIP-3009 USDC nor EIP-2612 kVCM), so no relay function applies. Use the chain's USDC or kVCM address.
404 no_candidatesNo credit in the carbon class matched the request filters, or the class holds no credits. Call discover to list live classes and credits, then retry with a carbonClass/creditToken from that response. Retryable because class inventory changes.
400 vintage_not_foundNo credit in the class carries the requested vintage. Pick one of the years in the error's availableVintages, or omit vintage to let the server choose a liquid credit.
422 insufficient_liquidityThe pool cannot fill the requested amount at any price right now. Reduce amount, choose another credit or class, or retry later. Retryable because pool depth changes block to block.
422 amount_not_whole_tonnesThe credit's registry (Puro) retires in whole tonnes only, and amount has a fractional part. Send an integer amount (e.g. "2", not "2.5").
422 amount_below_incrementamount is smaller than the credit's minimum retirement unit. Raise amount to at least the minimum reported in the error body.
400 puro_details_requiredThe credit is Puro-issued, whose registry requires consumption metadata that the request omitted. Add the fields named in the error body to details: beneficiaryLocation, consumptionCountryCode, consumptionPeriodStart, consumptionPeriodEnd.
402 payment_requiredNot a failure: the x402 challenge returned when actions/retire is posted without an authPayload. The body carries the EIP-712 typedData to sign and a ready-to-send actionsRetireRequest. Identical in shape to a prepare-auth 200. Sign typedData with the payer wallet, set authPayload.signature (or v/r/s), and POST actionsRetireRequest back — verbatim, including salt on the USDC path.
400 attribution_requiredA relayed retirement named no beneficiary. The beneficiary is indexed on-chain as a permanent grouping key and cannot be changed once the retirement confirms, so it is not defaulted silently. Set details.beneficiaryAddress to the party the retirement is for, or set beneficiaryIsPayer: true to credit the paying wallet deliberately.
400 invalid_auth_payloadThe authorization is structurally wrong for this request: authPayload.from is not the request from, authPayload.to is not the settlement contract, the payload shape doesn't match the input token's scheme (EIP-3009 for USDC, EIP-2612 for kVCM), or a USDC payload arrived without its top-level salt. Post the actionsRetireRequest from prepare-auth (or the 402 challenge) verbatim, adding only the signature. Do not rebuild the payload by hand.
400 insufficient_authorized_valueThe signed authPayload.value no longer covers retirement + protocol fee + executor gas, usually because price or gas moved after signing. Relaying it would revert on-chain. Re-run prepare-auth (or re-request the 402 challenge) to size a fresh budget of at least requiredMinimum, then re-sign. The old authorization is unusable, not merely stale.
400 params_mismatchThe submitted retirement is not the one that was authorized. On the USDC path authPayload.nonce is keccak256 of the retirement plus salt, so the signature binds the credit, amount, and attribution — not just the spend value. The rebuilt struct hashed to something else. Re-post actionsRetireRequest verbatim including creditToken, tokenId, details, and salt, or re-run prepare-auth and re-sign. A salt is single-use; one from an earlier authorization will not reproduce the nonce. The error echoes expectedNonce, receivedNonce, and the submitted values to diff against.
422 contract_revertA contract call reverted during simulation, so nothing was broadcast and no funds moved. selector and decoded.errorName identify the revert; contract, function, and args give the call context. Read decoded.errorName. Liquidity and slippage reverts are worth retrying with a fresh quote; validation and permission reverts are not.
422 transaction_revertedThe relayed transaction mined but reverted, typically from a state change between simulation and inclusion. No retirement was recorded. Inspect transactionHash on a block explorer, then re-run prepare-auth and re-sign. The old authorization's nonce may already be consumed.
503 gas_estimate_unavailableThe executor's gas reimbursement could not be priced, so the authorization budget cannot be sized. No retirement was attempted. Retry with backoff. Nothing was signed or spent, so the request can be repeated unchanged.

Runnable examples (USDC + kVCM) live in examples/: a read-only discover + quote, the minimal SDK retire, and a raw-HTTP protocol walkthrough.

Quickstart

Examples for GET paths, POST JSON, TypeScript, and the Base MCP plugin. API calls are free; you sign and broadcast the returned batch yourself. The protocol fee is settled on-chain inside that transaction.

# List carbon classes (optional filters)
curl "https://x402.klimalabs.com/api/discover?maxUsdcPricePerTonne=15"

# Live price for a tonnage
curl "https://x402.klimalabs.com/api/quote?chainId=8453\
&inputToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\
&carbonClass=0xf4699531e0a5f6e9351a36de3753deaad329bf45&amount=1.5"

# Unsigned approve + retire batch (you broadcast it)
curl "https://x402.klimalabs.com/api/prepare/retire?chainId=8453\
&inputToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\
&carbonClass=0xf4699531e0a5f6e9351a36de3753deaad329bf45&amount=1.5\
&details=%7B%22beneficiaryString%22%3A%22Acme%20Corp%22%7D"

# Carbonmark certificate after confirmation
curl "https://x402.klimalabs.com/api/certificate?txHash=0xYOUR_RETIRE_TX_HASH"

Inputs & contracts (Base mainnet)

ContractAddress
Input token: USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA029136 decimals
Input token: kVCM0x00fbac94fec8d4089d3fe979f39454f48c71a65d18 decimals
Retirement Aggregator0xda0a793d7c32ab80bcdab7f8c725c96db22464f4
AAM0x1C24239309398220883207681602BfF4D10fbde1
Settlement Contractread from the prepare response (to / approvalInstructions.spender)

Addresses are read from /discover. Amount rules: decimal tonne string, minimum 0.001 t (1 kg). Toucan Puro credits retire in whole tonnes only. Amounts above a credit's liquidity are rejected.

Fees

API calls are free. Each retirement bakes in a protocol fee — max(floor, feeBps% of cost), floor denominated in USDC (converted to kVCM via the pool when paying in kVCM) — computed and collected on-chain by the Settlement Contract. It's always included in quote.fee and folded into total. The contract spends exactly retirementCost + fee and refunds any unused slippage budget in the same transaction.

Versioning & releases

The API carries a semantic version — currently 1.0.0. The major is the compatibility contract, and the host is how you pin it.

HostServes
x402.klimalabs.comThe latest release. A future major bump moves it.
v1.x402.klimalabs.comThe v1 major and every future 1.x minor and patch. Never moves to the next major.
v0.x402.klimalabs.comThe previous major, kept reachable for callers that have not migrated. Not extended.

Everything on this page describes v1. Pinning the v<major>. host is the entire mechanism — there is no version header, query parameter, or Accept negotiation, and no way to pin a minor or patch: within a major you always get the newest build.

How changes are bundled

BumpContainsReaches a pinned caller?
patchFixes that leave the request and response contract intact.yes
minorAdditive only: new actions, new optional request fields, new response fields.yes
majorAnything a working caller could notice as a break.no

Breaking means a request that succeeded before now fails, or a response field is removed, renamed, or changes meaning — newly required fields and stricter validation included. Breaking changes accumulate and ship together in one deliberate major bump, so you migrate once per major instead of continuously. Additive work does not wait for that bump: it ships in a minor and reaches every caller on the major, so pinning does not freeze you out of new features.

Every release is an entry in the machine-readable /.well-known/x402-changelog.json with a breaking boolean, a per-change kind, and the action each change applies to. Breaking releases additionally carry a migration string stating concretely what to change. Both the manifest and the changelog publish apiVersion and a versionedHosts object, so an agent can resolve the right host without reading this table — poll that rather than diffing live responses.

Error reference

Every failure — and the 402 challenge — returns the same envelope: a string error code, a human message, an x402FacilitatorVersion number, and code-specific context fields (issues on schema_validation, expectedNonce/receivedNonce on params_mismatch, and so on). Match on error, never on the message — codes are stable, wording is not.

retry means the identical request can succeed later untouched; fix request means change it first. This table is read from /.well-known/x402-errors.json — build error handling against that document rather than this page.

Request
400 invalid_jsonThe request body was not parseable JSON. Send a JSON object with a content-type: application/json header. Note the API takes a single POST body, not form-encoded fields.fix request
400 unknown_actionThe body's action field is missing or is not one of the supported actions. Set action to one of the values in supported (echoed in the error body), or GET the endpoint root for the action index.fix request
400 schema_validationThe body failed schema validation. issues carries the offending path and reason. Bodies are strict at the top level and inside details, so an unrecognized key is an error rather than being silently dropped. Read issues[].path and issues[].keys. For unrecognized_keys, check the key belongs where you put it — attribution fields go inside details, not at the top level.fix request
404 not_foundNo route exists at the requested path. This API is a single POST multiplexer: POST the endpoint URL with an action field rather than using per-action paths. The 404 body carries the endpoint and action list.fix request
404 document_not_foundNo documentation document with the requested id (from /api/docs?id=…). Use one of the ids in the error's available list, or fetch the index at /api/docs.fix request
500 internal_errorAn unhandled server-side failure. Retry with backoff. If it persists, report it via the contact in /.well-known/security.txt with the request body.retry
Credit resolution
400 unsupported_chain_idchainId is not a supported network. Use 8453 (Base mainnet) or 84532 (Base Sepolia).fix request
400 unsupported_input_tokeninputToken is not an accepted payment token on this chain. Use the USDC or kVCM address for the chain — see the manifest, or the addresses in the endpoint documentation.fix request
400 invalid_input_tokeninputToken passed validation but matches neither settlement path (EIP-3009 USDC nor EIP-2612 kVCM), so no relay function applies. Use the chain's USDC or kVCM address.fix request
400 vintage_not_foundNo credit in the class carries the requested vintage. Pick one of the years in the error's availableVintages, or omit vintage to let the server choose a liquid credit.fix request
404 no_candidatesNo credit in the carbon class matched the request filters, or the class holds no credits. Call discover to list live classes and credits, then retry with a carbonClass/creditToken from that response. Retryable because class inventory changes.retry
Amount rules
400 puro_details_requiredThe credit is Puro-issued, whose registry requires consumption metadata that the request omitted. Add the fields named in the error body to details: beneficiaryLocation, consumptionCountryCode, consumptionPeriodStart, consumptionPeriodEnd.fix request
422 insufficient_liquidityThe pool cannot fill the requested amount at any price right now. Reduce amount, choose another credit or class, or retry later. Retryable because pool depth changes block to block.retry
422 amount_not_whole_tonnesThe credit's registry (Puro) retires in whole tonnes only, and amount has a fractional part. Send an integer amount (e.g. "2", not "2.5").fix request
422 amount_below_incrementamount is smaller than the credit's minimum retirement unit. Raise amount to at least the minimum reported in the error body.fix request
Authorization (relay path)
400 attribution_requiredA relayed retirement named no beneficiary. The beneficiary is indexed on-chain as a permanent grouping key and cannot be changed once the retirement confirms, so it is not defaulted silently. Set details.beneficiaryAddress to the party the retirement is for, or set beneficiaryIsPayer: true to credit the paying wallet deliberately.fix request
400 invalid_auth_payloadThe authorization is structurally wrong for this request: authPayload.from is not the request from, authPayload.to is not the settlement contract, the payload shape doesn't match the input token's scheme (EIP-3009 for USDC, EIP-2612 for kVCM), or a USDC payload arrived without its top-level salt. Post the actionsRetireRequest from prepare-auth (or the 402 challenge) verbatim, adding only the signature. Do not rebuild the payload by hand.fix request
400 insufficient_authorized_valueThe signed authPayload.value no longer covers retirement + protocol fee + executor gas, usually because price or gas moved after signing. Relaying it would revert on-chain. Re-run prepare-auth (or re-request the 402 challenge) to size a fresh budget of at least requiredMinimum, then re-sign. The old authorization is unusable, not merely stale.fix request
400 params_mismatchThe submitted retirement is not the one that was authorized. On the USDC path authPayload.nonce is keccak256 of the retirement plus salt, so the signature binds the credit, amount, and attribution — not just the spend value. The rebuilt struct hashed to something else. Re-post actionsRetireRequest verbatim including creditToken, tokenId, details, and salt, or re-run prepare-auth and re-sign. A salt is single-use; one from an earlier authorization will not reproduce the nonce. The error echoes expectedNonce, receivedNonce, and the submitted values to diff against.fix request
402 payment_requiredNot a failure: the x402 challenge returned when actions/retire is posted without an authPayload. The body carries the EIP-712 typedData to sign and a ready-to-send actionsRetireRequest. Identical in shape to a prepare-auth 200. Sign typedData with the payer wallet, set authPayload.signature (or v/r/s), and POST actionsRetireRequest back — verbatim, including salt on the USDC path.fix request
Settlement
404 retirement_not_foundNo indexed retirement for that transaction hash. Immediately after confirmation this means the subgraph has not caught up yet, not that the retirement failed. Poll every few seconds. If a retirement response returned pending_index, this is the expected interim state.retry
422 contract_revertA contract call reverted during simulation, so nothing was broadcast and no funds moved. selector and decoded.errorName identify the revert; contract, function, and args give the call context. Read decoded.errorName. Liquidity and slippage reverts are worth retrying with a fresh quote; validation and permission reverts are not.retry
422 transaction_revertedThe relayed transaction mined but reverted, typically from a state change between simulation and inclusion. No retirement was recorded. Inspect transactionHash on a block explorer, then re-run prepare-auth and re-sign. The old authorization's nonce may already be consumed.retry
Upstream
503 gas_estimate_unavailableThe executor's gas reimbursement could not be priced, so the authorization budget cannot be sized. No retirement was attempted. Retry with backoff. Nothing was signed or spent, so the request can be repeated unchanged.retry

Every action can additionally return: invalid_json, unknown_action, not_found, document_not_found, schema_validation, internal_error.

On-chain failures all surface as contract_revert, with the specific revert decoded onto the response as selector and decoded.errorName.

Use it from an AI agent

The fastest path is the Klima Base MCP plugin — it wires this endpoint into any Base MCP–capable agent (e.g. Claude Code).

  1. Install — connect Base MCP and add the Klima plugin by following the Base MCP setup guide. Plugin: plugins/klima-retire.md.
  2. Ask in plain language — e.g. “Retire 2 tonnes of carbon under $15/tonne, beneficiary ‘Acme Corp’.”
  3. The agent runs discover quote prepare/retire → shows you the cost → submits one wallet approval → returns your certificate URL.

Prefer a direct integration (no MCP)? Call the GET or POST endpoints above yourself, then sign and submit the prepare/retire batch (approve + retirement) with your own wallet on Base — or use the gasless paid relay path (sign one authorization, a Klima executor submits it).

Retirement is irreversible

It permanently burns the carbon credit — no undo, refund, or resale once the transaction confirms. Always review the quoted tonnes, price, and fee before approving.

Resources

Endpoint operated by Klima Protocol · Live status refreshes every 60 seconds