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.
- Chain
- Base mainnet (chainId=8453)
- Auth
- Free GET or POST; on-chain protocol fee only
- Pin a version
- v1.x402.klimalabs.com
- Agent manifest
- /.well-known/x402.json
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):
discover→quote→prepare/retirehands 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.
| Method | Path | Fee | Purpose |
|---|---|---|---|
| GET / POST | /discover | free | List retirable carbon classes, credits, and reference prices. |
| GET / POST | /quote | free | Live on-chain price for a tonnage in USDC or kVCM. |
| GET / POST | /prepare/retire | free | Unsigned approve + retire batch for atomic submission. |
| POST | /prepare-auth | free | Build the EIP-712 authorization to sign for a relayed retirement. |
| POST | /actions/retire | variable | Relay a signed authorization on-chain and return the certificate. |
| GET / POST | /certificate | free | Resolve Carbonmark certificate URL(s) after confirmation. |
| GET | /api/health | free | Liveness probe. Always 200, no payment required. |
| GET | /.well-known/x402.json | free | Agent-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.
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):
| Field | Type | Meaning |
|---|---|---|
| retiringAddress | string | address performing the retirement |
| retiringEntityString | string | retiring-entity display name |
| beneficiaryAddress | string | address credited on the certificate |
| beneficiaryString | string | beneficiary display name — shows on the certificate |
| retirementMessage | string | public message on the certificate |
| beneficiaryLocation | string | Toucan Puro only — required for Puro credits |
| consumptionCountryCode | string | Toucan Puro only — required for Puro credits |
| consumptionPeriodStart | string | number | Toucan Puro only — required for Puro credits |
| consumptionPeriodEnd | string | number | Toucan 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.
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.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);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
| status | Meaning |
|---|---|
| settled | Mined and indexed — retirements[] carries the certificate URL(s). |
| pending_index | Mined (or broadcast) but subgraph not caught up — poll /certificate with transactionHash. |
| 400 unsupported_chain_id | chainId is not a supported network. Use 8453 (Base mainnet) or 84532 (Base Sepolia). |
| 400 unsupported_input_token | inputToken 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_token | inputToken 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_candidates | No 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_found | No 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_liquidity | The 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_tonnes | The 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_increment | amount is smaller than the credit's minimum retirement unit. Raise amount to at least the minimum reported in the error body. |
| 400 puro_details_required | The 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_required | Not 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_required | A 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_payload | The 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_value | The 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_mismatch | The 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_revert | A 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_reverted | The 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_unavailable | The 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)
| Contract | Address |
|---|---|
| Input token: USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA029136 decimals |
| Input token: kVCM | 0x00fbac94fec8d4089d3fe979f39454f48c71a65d18 decimals |
| Retirement Aggregator | 0xda0a793d7c32ab80bcdab7f8c725c96db22464f4 |
| AAM | 0x1C24239309398220883207681602BfF4D10fbde1 |
| Settlement Contract | read 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.
| Host | Serves |
|---|---|
| x402.klimalabs.com | The latest release. A future major bump moves it. |
| v1.x402.klimalabs.com | The v1 major and every future 1.x minor and patch. Never moves to the next major. |
| v0.x402.klimalabs.com | The 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
| Bump | Contains | Reaches a pinned caller? |
|---|---|---|
| patch | Fixes that leave the request and response contract intact. | yes |
| minor | Additive only: new actions, new optional request fields, new response fields. | yes |
| major | Anything 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_json | The 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_action | The 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_validation | The 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_found | No 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_found | No 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_error | An 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_id | chainId is not a supported network. Use 8453 (Base mainnet) or 84532 (Base Sepolia). | fix request |
| 400 unsupported_input_token | inputToken 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_token | inputToken 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_found | No 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_candidates | No 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_required | The 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_liquidity | The 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_tonnes | The 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_increment | amount 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_required | A 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_payload | The 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_value | The 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_mismatch | The 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_required | Not 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_found | No 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_revert | A 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_reverted | The 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_unavailable | The 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).
- Install — connect Base MCP and add the Klima plugin by following the Base MCP setup guide. Plugin: plugins/klima-retire.md.
- Ask in plain language — e.g. “Retire 2 tonnes of carbon under $15/tonne, beneficiary ‘Acme Corp’.”
- 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
Agent plugin + setup docs
Base MCP setup guide, klima-retire plugin, and full integration docs.
Base MCP setup guide
Connect Base MCP and add the Klima retirement plugin.
Discovery manifest
Public x402.json that agent crawlers index automatically.
Changelog
Machine-readable release history — per-release breaking flag and a migration string on every breaking one.
Error registry
Every error code with its status, group, retryable flag, and remedy — build error handling against this.
Client SDK (klima-retire.ts)
Zero-dependency, signer-agnostic TypeScript client — one retire() call runs the whole relay flow.
Runnable examples
Discover + quote (no wallet), the minimal SDK retire, and a raw-HTTP protocol walkthrough.
Klima Aggregator USAGE
Direct on-chain access for callers who do not want the HTTP layer.
x402 protocol
The HTTP 402 Payment Required protocol this endpoint implements.
Base MCP
Base AI agent quickstart — wallet-connected agents on Base mainnet.