{"service":"Klima Retirement Aggregator","apiVersion":"1.0.0","envelope":{"description":"Every non-2xx response (and the 402 challenge) carries a string `error` field holding one of the codes below, plus code-specific context fields documented in `remedy`.","example":{"error":"attribution_required","message":"…","x402FacilitatorVersion":2}},"codes":[{"code":"invalid_json","status":400,"group":"request","actions":"*","retryable":false,"meaning":"The request body was not parseable JSON.","remedy":"Send a JSON object with a `content-type: application/json` header. Note the API takes a single POST body, not form-encoded fields."},{"code":"unknown_action","status":400,"group":"request","actions":"*","retryable":false,"meaning":"The body's `action` field is missing or is not one of the supported actions.","remedy":"Set `action` to one of the values in `supported` (echoed in the error body), or GET the endpoint root for the action index."},{"code":"not_found","status":404,"group":"request","actions":"*","retryable":false,"meaning":"No route exists at the requested path.","remedy":"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."},{"code":"document_not_found","status":404,"group":"request","actions":"*","retryable":false,"meaning":"No documentation document with the requested `id` (from `/api/docs?id=…`).","remedy":"Use one of the ids in the error's `available` list, or fetch the index at /api/docs."},{"code":"schema_validation","status":400,"group":"request","actions":"*","retryable":false,"meaning":"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.","remedy":"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."},{"code":"internal_error","status":500,"group":"request","actions":"*","retryable":true,"meaning":"An unhandled server-side failure.","remedy":"Retry with backoff. If it persists, report it via the contact in /.well-known/security.txt with the request body."},{"code":"unsupported_chain_id","status":400,"group":"resolution","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"`chainId` is not a supported network.","remedy":"Use 8453 (Base mainnet) or 84532 (Base Sepolia)."},{"code":"unsupported_input_token","status":400,"group":"resolution","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"`inputToken` is not an accepted payment token on this chain.","remedy":"Use the USDC or kVCM address for the chain — see the manifest, or the addresses in the endpoint documentation."},{"code":"invalid_input_token","status":400,"group":"resolution","actions":["actions/retire"],"retryable":false,"meaning":"`inputToken` passed validation but matches neither settlement path (EIP-3009 USDC nor EIP-2612 kVCM), so no relay function applies.","remedy":"Use the chain's USDC or kVCM address."},{"code":"no_candidates","status":404,"group":"resolution","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":true,"meaning":"No credit in the carbon class matched the request filters, or the class holds no credits.","remedy":"Call `discover` to list live classes and credits, then retry with a `carbonClass`/`creditToken` from that response. Retryable because class inventory changes."},{"code":"vintage_not_found","status":400,"group":"resolution","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"No credit in the class carries the requested `vintage`.","remedy":"Pick one of the years in the error's `availableVintages`, or omit `vintage` to let the server choose a liquid credit."},{"code":"insufficient_liquidity","status":422,"group":"amount","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":true,"meaning":"The pool cannot fill the requested amount at any price right now.","remedy":"Reduce `amount`, choose another credit or class, or retry later. Retryable because pool depth changes block to block."},{"code":"amount_not_whole_tonnes","status":422,"group":"amount","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"The credit's registry (Puro) retires in whole tonnes only, and `amount` has a fractional part.","remedy":"Send an integer `amount` (e.g. \"2\", not \"2.5\")."},{"code":"amount_below_increment","status":422,"group":"amount","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"`amount` is smaller than the credit's minimum retirement unit.","remedy":"Raise `amount` to at least the minimum reported in the error body."},{"code":"puro_details_required","status":400,"group":"amount","actions":["prepare/retire","prepare-auth","actions/retire"],"retryable":false,"meaning":"The credit is Puro-issued, whose registry requires consumption metadata that the request omitted.","remedy":"Add the fields named in the error body to `details`: `beneficiaryLocation`, `consumptionCountryCode`, `consumptionPeriodStart`, `consumptionPeriodEnd`."},{"code":"payment_required","status":402,"group":"authorization","actions":["actions/retire"],"retryable":false,"meaning":"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.","remedy":"Sign `typedData` with the payer wallet, set `authPayload.signature` (or `v`/`r`/`s`), and POST `actionsRetireRequest` back — verbatim, including `salt` on the USDC path."},{"code":"attribution_required","status":400,"group":"authorization","actions":["prepare-auth","actions/retire"],"retryable":false,"meaning":"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.","remedy":"Set `details.beneficiaryAddress` to the party the retirement is for, or set `beneficiaryIsPayer: true` to credit the paying wallet deliberately."},{"code":"invalid_auth_payload","status":400,"group":"authorization","actions":["actions/retire"],"retryable":false,"meaning":"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`.","remedy":"Post the `actionsRetireRequest` from `prepare-auth` (or the 402 challenge) verbatim, adding only the signature. Do not rebuild the payload by hand."},{"code":"insufficient_authorized_value","status":400,"group":"authorization","actions":["actions/retire"],"retryable":false,"meaning":"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.","remedy":"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."},{"code":"params_mismatch","status":400,"group":"authorization","actions":["actions/retire"],"retryable":false,"meaning":"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.","remedy":"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."},{"code":"contract_revert","status":422,"group":"settlement","actions":["quote","prepare/retire","prepare-auth","actions/retire"],"retryable":true,"meaning":"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.","remedy":"Read `decoded.errorName`. Liquidity and slippage reverts are worth retrying with a fresh quote; validation and permission reverts are not."},{"code":"transaction_reverted","status":422,"group":"settlement","actions":["actions/retire"],"retryable":true,"meaning":"The relayed transaction mined but reverted, typically from a state change between simulation and inclusion. No retirement was recorded.","remedy":"Inspect `transactionHash` on a block explorer, then re-run `prepare-auth` and re-sign. The old authorization's nonce may already be consumed."},{"code":"retirement_not_found","status":404,"group":"settlement","actions":["certificate"],"retryable":true,"meaning":"No indexed retirement for that transaction hash. Immediately after confirmation this means the subgraph has not caught up yet, not that the retirement failed.","remedy":"Poll every few seconds. If a retirement response returned `pending_index`, this is the expected interim state."},{"code":"gas_estimate_unavailable","status":503,"group":"upstream","actions":["prepare-auth","actions/retire"],"retryable":true,"meaning":"The executor's gas reimbursement could not be priced, so the authorization budget cannot be sized. No retirement was attempted.","remedy":"Retry with backoff. Nothing was signed or spent, so the request can be repeated unchanged."}],"manifest":"https://x402.klimalabs.com/.well-known/x402.json"}