Migrating from v2.6 to v3
This guide covers every breaking change between ekko Climate API v2.6 and v3, with a practical playbook for migrating your integration. Work through each section in order, then validate against staging before cutting over.
Who this is for
Commercial partners with a working v2.6 integration. If you're starting fresh, see Getting started instead.
What's changed at a glance
Each row links to the section explaining the change.
| Area | v2.6 | v3 |
|---|---|---|
| Authentication | X-Api-Key header | Authorization: Bearer |
| Base URL | api.ekko.earth/v2.6 | api.ekko.earth/v3 |
| Organisation identifier | productId | organisationId |
| Consumer identifier | customerId (string) | consumer object |
| Transaction amount | amount.value | transaction.amount |
| Credits field | compensation | credits |
| Address fields | street, country | line1, countryCode |
| Organisation onboarding | Two endpoints, brand/product model | Single POST /organisations, nested hierarchies |
| Footprint calculation | POST /impact_calculator | POST /quotes/carbon |
| Quote reference | calcReference | quoteId |
| Funds allocation | POST /funds | POST /funds/allocations |
| Funds reference | reference | fundsAllocationId |
| Reversals | POST /reversal (body param) | POST /funds/allocations/{id}/reverse (path param) |
| Reconciliation | POST /impact_records | GET /funds/allocations/impact and GET /funds/allocations/profit-share |
| Projects | GET /products/{productId}/projects | GET /impact-partners/projects |
| SDK sessions | checkoutSessionId, impactPaySessionId | sessionId, quoteId, expiresAt |
| Success status codes | 200 for all POSTs | 201 for all POSTs |
| Error envelope | Nested error object with statusCode, message, errorName, details, path, requestId, timestamp | Flat {error, code} |
| Webhooks | Unchanged today | In active review |
How to migrate
Work through these four phases in order. Each one builds on the last and gives you a clean checkpoint before moving on.
1. Review terminology
Read Key concepts so the v3 vocabulary is settled before you start coding. The biggest shifts are compensation → credits, the introduction of organisation (replacing the brand/product model) and the separation of credits from contributions.
2. Update authentication and endpoints
Switch your HTTP client to send Authorization: Bearer instead of X-Api-Key, update the base URL, then work through the endpoint sections below. Update field names in your request builders, then the response parsers, then your status code and error handling.
3. Run a parallel sandbox test
Point your integration at the v3 sandbox (https://staging.api.ekko.earth/v3) and run your full transaction flow end-to-end alongside your existing v2.6 traffic. Compare quote outputs, allocation records and reconciliation totals for the same input transactions across both versions. Treat any unexplained mismatch as a blocker.
The Direct API and Checkout SDK integration check collections run automated assertions across the v3 sandbox and give you a 30-second pass/fail signal. See Test cases.
4. Cut over
Once sandbox parity is solid, swap the production base URL to https://api.ekko.earth/v3 and ship. v2.6 keeps running in parallel, so you can roll back without coordination if you spot an issue.
Authentication
v2.6 used a custom header:
X-Api-Key: your_api_key_herev3 uses a standard Bearer token in the Authorization header:
Authorization: Bearer your_api_key_hereUpdate every request to use the new header format. Your API key value does not change. For full details on key management and IP allowlisting, see Authentication and security.
Base URL
Replace the v2.6 base URL across your entire integration.
| Environment | URL |
|---|---|
| Production (v2.6) | https://api.ekko.earth/v2.6 |
| Production (v3) | https://api.ekko.earth/v3 |
| Sandbox (v3) | https://staging.api.ekko.earth/v3 |
Status codes
Every POST endpoint that returned 200 OK in v2.6 returns 201 Created in v3. This applies to /quotes/carbon, /funds/allocations, /funds/allocations/{id}/reverse, /checkout/sessions, /impactpay/sessions, /post-purchase/sessions and /organisations. GET endpoints continue to return 200.
If your integration checks for an exact status code (status === 200), update it to accept 201 for POST responses or check the 2xx range.
Terminology and identifier changes
These changes affect every endpoint. Apply them first, before tackling individual endpoint sections.
productId → organisationId
Every v2.6 request that included a productId now uses organisationId. The value maps directly: your v2.6 productId is the organisationId of your primary organisation in v3.
customerId → consumer object
v2.6 identified consumers as a flat string. v3 uses a consumer object so tax jurisdictions and project matching can use location data.
v2.6:
{
"customerId": "customer_a"
}v3:
{
"consumer": {
"reference": "customer_a",
"countryCode": "GBR",
"postalCode": "SW1A 1AA",
"city": "London"
}
}reference and countryCode are required. countryCode is an ISO 3166-1 alpha-3 code (GBR, USA, AUS). postalCode, city and state are optional, but providing them unlocks location-matched project filtering (see New in v3).
amount → transaction object
v2.6 wrapped the transaction amount in an amount object with a value field. v3 uses a transaction object with the amount inline.
v2.6:
{
"amount": {
"value": 169.99,
"currencyCode": "GBP"
}
}v3:
{
"transaction": {
"amount": 169.99,
"currencyCode": "GBP"
}
}compensation → credits
The field name compensation is replaced by credits across all v3 request and response bodies. v3 treats credits and contributions as two distinct mechanisms: credits are tied to specific carbon credit projects, contributions go to impact partners. The rename applies to the quote response, the funds allocation request and the funds allocation response.
Address schema
v3 uses a consistent address structure across all endpoints. Two things change from v2.6:
street(andaddress1/address2/address3in onboarding requests) becomesline1,line2,line3countrybecomescountryCodeand requires a three-character ISO 3166-1 alpha-3 code
v2.6:
{
"address": {
"street": "214 Oxford Street",
"city": "London",
"postalCode": "W1D 1LA",
"country": "GBR"
}
}v3:
{
"address": {
"line1": "214 Oxford Street",
"city": "London",
"postalCode": "W1D 1LA",
"countryCode": "GBR"
}
}line2 and line3 are optional and nullable.
Organisation management
Request changes
v2.6 provided two onboarding endpoints:
POST /organisations/brand/onboarding: full onboarding with legal and billing detailsPOST /organisations/brand/initial_onboarding: reduced field set for faster onboarding
Both are removed in v3. Use a single POST /organisations endpoint instead:
{
"parentOrganisationId": "9e1d3a52-0e3d-4a9f-8b6e-1c2d3e4f5a6b",
"tradingName": "Blossom & Bloom",
"legalName": "BB Flowers Ltd",
"address": {
"line1": "Ground floor",
"line2": "45 Rosewood Avenue",
"city": "London",
"postalCode": "SW1A 1AA",
"countryCode": "GBR"
},
"currencyCode": "GBP",
"mcc": "5992",
"organisationReference": "MID1234567"
}v3 supports nested organisation hierarchies of any depth. You can model structures such as platform → regional office → individual organisation by chaining parentOrganisationId references. Each organisation inherits settings from its parent unless explicitly overridden.
Storing an MCC at organisation level
Setting
mccon the organisation lets you omit themerchantobject from quote and SDK session requests.
Field mapping from v2.6
| v2.6 field | v3 field |
|---|---|
tradingName | tradingName |
legalName | legalName |
currency | currencyCode |
billingEmail | billing.email |
companyRegistrationNumber | billing.companyRegistrationNumber |
vatNumber | billing.taxNumber |
brandReference | organisationReference |
brandOwner | Set parentOrganisationId to the owner organisation's ID |
brandBilling: true | Include a billing object with required billing fields |
brandBilling: false | Omit the billing object; the nearest billable parent is invoiced |
tradingAddress and legalAddress | Single address field |
Response changes
v2.6 returned {acknowledged, message, organisationId, brandId, productId, projects}. v3 returns the organisation record directly with organisationId as the primary identifier. brandId and productId are removed. Store the organisationId and use it in all subsequent requests. You can retrieve an existing organisation using GET /organisations/{id}.
The success status code changes from 200 to 201.
Calculating footprint
Request changes
POST /impact_calculator becomes POST /quotes/carbon. The request adopts the identifier and structural changes from Terminology and identifier changes, and adds a required locale field.
v2.6:
{
"productId": "c8f514a4-beb9-4d8d-8673-da82feff731e",
"customerId": "customer_a",
"amount": {
"value": 169.99,
"currencyCode": "GBP"
},
"merchant": {
"mcc": "5651",
"name": "Oxford Street Clothing Co.",
"address": {
"street": "214 Oxford Street",
"city": "London",
"postalCode": "W1D 1LA",
"country": "GBR"
}
}
}v3:
{
"organisationId": "2f6d6b1f-1c6a-4e9a-9c15-7b8a0db1a2c3",
"locale": "en-GB",
"consumer": {
"reference": "customer_a",
"countryCode": "GBR",
"postalCode": "W1D 1LA",
"city": "London"
},
"merchant": {
"name": "Oxford Street Clothing Co.",
"mcc": "5651",
"address": {
"line1": "214 Oxford Street",
"city": "London",
"postalCode": "W1D 1LA",
"countryCode": "GBR"
}
},
"transaction": {
"amount": 169.99,
"currencyCode": "GBP"
}
}locale is required. If the requested locale isn't supported, the API defaults to en-GB. calcReference is renamed to quoteId. You must pass quoteId when allocating funds, and it locks in pricing and tax rates at the time of calculation.
Response changes
The v2.6 carbonImpact response (with grams, ounces, equivalents, compensation and roundUp) becomes a three-part quote with footprint, credits and contribution:
footprintcontainsco2eGrams,co2eOuncesand localisedequivalentscreditscontains pricing (totalAmount,impactAmount,impactSalesTaxAmount,serviceFeeAmount,serviceFeeSalesTaxAmount) and impact partner detailscontributioncontains percentage-based pricing (impactPercentage,serviceFeePercentage,impactSalesTaxPercentage,serviceFeeSalesTaxPercentage) and impact partner details
The v2.6 roundUp object is not present in the v3 response. For round-up flows, apply the percentages from the contribution section to your chosen round-up amount. The success status code changes from 200 to 201. See Create a carbon quote for the full response reference.
Allocating funds
Request changes
POST /funds becomes POST /funds/allocations. v3 separates credits from contributions: compensation is renamed to credits, projectIds belongs to credits only, and contributions target an impact partner via impactPartnerIds. currencyCode moves to the top level.
v2.6:
{
"productId": "c8f514a4-beb9-4d8d-8673-da82feff731e",
"calcReference": "e9de9d42-74f1-400c-8bd2-fbc91944c2ef",
"compensation": {
"value": 0.47,
"currencyCode": "GBP"
},
"contribution": {
"value": 0.53,
"currencyCode": "GBP"
}
}v3:
{
"organisationId": "2f6d6b1f-1c6a-4e9a-9c15-7b8a0db1a2c3",
"quoteId": "d4e5f6a7-2b3c-4d8e-9f1a-5b6c7d8e9f0a",
"currencyCode": "GBP",
"credits": {
"amount": 0.47,
"projectIds": ["5d3f4d1a-c911-490e-8dcd-6b9445be20e6"]
},
"contribution": {
"amount": 0.53,
"impactPartnerIds": ["9405545f-a850-4427-ab0d-10b5b734e925"]
}
}You must send at least one of credits or contribution. Both in the same request is allowed.
Routing without project or partner IDs
If you omit
projectIdsfromcreditsorimpactPartnerIdsfromcontribution, ekko allocates the funds across the projects and impact partners returned in the original quote response. The quote reflects your organisation's configured defaults, so you'll get the routing you've set up on your organisation without needing to pass the IDs explicitly.
Response changes
The response shape is different in three ways that will break v2.6 parsers:
- Identifier: v2.6 returned
{acknowledged, reference, compensation, contribution}. v3 returnsfundsAllocationIdas the primary identifier. Theacknowledgedboolean is removed. - Granular breakdown: v3 returns
creditsandcontributionsarrays with per-line entries, each containing the impact amount, service fee and granular sales tax fields (impactSalesTaxAmount,impactSalesTaxRate,serviceFeeSalesTaxAmount,serviceFeeSalesTaxRate). - Tax liability: a new top-level
taxLiabilityfield (platformororganisation) indicates which party holds sales tax responsibility for the allocation.
The success status code changes from 200 to 201. See Allocate funds to a project for the full response example.
Reversals
Request changes
POST /reversal becomes POST /funds/allocations/{id}/reverse. v3 uses the fundsAllocationId as a path parameter. No request body is required:
POST /funds/allocations/b7e9c4a2-8f31-4d6e-a529-3c7b8e1f0d42/reverseResponse changes
v2.6 returned {acknowledged, reference, message}. v3 returns the same shape as a funds allocation response, with:
- All amounts negated
- The original
fundsAllocationIdpreserved - A new
reversalIdfield identifying the reversal record
The success status code changes from 200 to 201. See Reverse a funds allocation for the full response example.
SDK sessions
Checkout SDK
The endpoint path POST /checkout/sessions is unchanged.
Request changes
Apply the identifier changes from Terminology and identifier changes: productId → organisationId, customerId (string) → consumer object, amount → transaction.
Response changes
| v2.6 field | v3 field |
|---|---|
checkoutSessionId | sessionId |
calcReference | quoteId |
clientSecret | clientSecret (unchanged) |
| (not present) | expiresAt |
The session expires at the expiresAt timestamp. Create a new session if the current one has expired. The success status code changes from 200 to 201. See Create a checkout SDK session for the full response.
ImpactPay
The endpoint path POST /impactpay/sessions is unchanged.
Request changes
Apply the identifier changes from Terminology and identifier changes, and add a required locale field.
Response changes
The response shape changes significantly. v2.6 returned only {impactPaySessionId, clientSecret}. v3 returns the hosted link plus footprint and credit data so you can show the consumer their impact before they click through.
| v2.6 field | v3 field |
|---|---|
impactPaySessionId | quoteId |
clientSecret | (removed) |
| (not present) | impactPayLink |
| (not present) | footprint (full footprint object with equivalents) |
| (not present) | credits.totalAmount |
| (not present) | locale, currencyCode, expiresAt |
The POST /impactpay/calculate endpoint (v2.6 hosted URL calculator) is removed. Use POST /impactpay/sessions to obtain both the hosted ImpactPay link and footprint data in a single call. The success status code changes from 200 to 201. See Create an ImpactPay session for the full response.
Post-purchase SDK
Post-purchase SDK is in active review
The v3 post-purchase SDK and
POST /post-purchase/sessionsendpoint are still being finalised. The shape below reflects current behaviour and is likely to change. If you plan to integrate post-purchase on v3, talk to your ekko contact first.
POST /post-purchase/sessions is new in v3. It creates a session for embedded or takeover post-purchase experiences and accepts an sdkVariant field (embedded or takeover). The request shape follows the same identifier conventions as the other session endpoints. The success status code is 201. See Create a post-purchase SDK session.
Reconciliation
v2.6's single POST /impact_records endpoint is replaced by two distinct GET endpoints in v3: impact recon (what credits and contributions were delivered per allocation) and profit share recon (how the service fee was distributed across the organisation hierarchy).
Request changes
| v2.6 | v3 |
|---|---|
POST /impact_records with filters in body | GET /funds/allocations/impact with filters as query parameters |
POST /impact_records/transaction | GET /funds/allocations/{id}/impact |
v2.6 (POST body):
{
"dateTimeFrom": "2025-05-01T00:00:00Z",
"dateTimeTo": "2025-06-01T00:00:00Z",
"brandId": "9c7b2c51-36f0-4934-8428-6a77e9b01af2"
}v3 (GET query parameters):
GET /funds/allocations/impact?dateTimeFrom=2025-05-01T00:00:00Z&dateTimeTo=2025-06-01T00:00:00Z&fundsOrganisationId=2f6d6b1f-1c6a-4e9a-9c15-7b8a0db1a2c3v3 also adds a separate GET /funds/allocations/profit-share endpoint for the service fee distribution across the organisation hierarchy. This data was bundled into clientServiceFeeShare and ekkoServiceFeeShare on each v2.6 record.
Response changes
The record shape is different in v3. The most important changes:
| v2.6 field | v3 equivalent |
|---|---|
transactionReferenceId | fundsAllocationId |
transactionDate | fundsDateTime |
brandId and brandTradingName | fundsOrganisationId and fundsOrganisationReference |
customerId | consumerReference |
impactAmount | amounts.impact |
serviceFee | amounts.serviceFee |
clientServiceFeeShare and ekkoServiceFeeShare | Available on the new GET /funds/allocations/profit-share endpoint |
fundsFlow (receivable/payable) | Removed. Settlement direction is now determined by your configured settlement model (net split, gross split or invoice). See Reconciliation. |
recordType | Replaced by separate credits and contribution arrays on each record |
v3 records also add:
- An allocation-level
amountsobject (total,impact,impactTax,serviceFee,serviceFeeTax) that mirrors the quote response, so you can compare actuals against the original quote usingquoteId unitper line item (e.g.kgCO2e,bottles) with aquantityimpactPartnerIdper line item- A
taxLiabilityfield (platformororganisation) indicating sales tax responsibility. This is a new concept, not a rename offundsFlow
Reversal records appear inline when they fall within the requested date range, identified by reversal: true with all amounts negated. See Reconciliation for the full response reference.
Projects and impact partners
Request changes
GET /products/{productId}/projects is replaced by two endpoints:
| v3 endpoint | Purpose |
|---|---|
GET /impact-partners/projects | List projects across all impact partners |
GET /impact-partners | List impact partners with name, logo, descriptions and website |
Both endpoints support cursor-based pagination (startingAfter, endingBefore, limit).
Response changes
v2.6 returned a single response object with two arrays: compensation (carbon credit projects) and sustainability (contribution projects). Each project contained projectId, name, description, image, url and a unit object.
v3 returns a single flat list of projects. Each project adds the following fields:
type:carbon_credits,nature_creditsorcontributiontheme:pollution,climate_stress,land_useorwater_uselocation:countryCode,regionandstatesdgs: array of associated UN Sustainable Development Goalssubtype: for carbon credit projects (e.g.sequestration,carbon removal)shortDescriptionandlongDescriptionas separate fieldsimpactPartnerIdlinking each project to its impact partner
See Impact partners and projects for the current impact partner list.
Error responses
The error response envelope changes in v3. v2.6 returned a nested error object with multiple metadata fields. v3 returns a flat envelope with the error message and a stable code.
v2.6:
{
"error": {
"statusCode": 400,
"message": "Bad Request Exception",
"errorName": "BadRequestException",
"details": {
"message": [
"amount.value must be a positive number"
],
"error": "Bad Request",
"statusCode": 400
},
"path": "/v2.6/impact_calculator",
"requestId": "7AkD3H",
"timestamp": "2025-09-09T16:51:00.789Z"
}
}v3:
{
"error": "invalid request body: amount.value must be a positive number",
"code": "BAD_REQUEST"
}Key changes:
- The error is a flat object, not a nested one. Anywhere your code reads
error.statusCode,error.message,error.errorName,error.details,error.path,error.requestIdorerror.timestampmust be updated. codeis derived from the HTTP status code by default (BAD_REQUEST,UNAUTHORIZED,FORBIDDEN,NOT_FOUND,CONFLICT,REQUEST_ENTITY_TOO_LARGE,UNPROCESSABLE_ENTITY,INTERNAL_SERVER_ERROR). Some errors return a more granular domain code (for exampleMAX_KEYS_REACHEDinstead ofCONFLICT) when additional context is available.- All v3 error messages are lowercase for consistency.
Use the HTTP status code as your primary signal for handling. Read code when you need to react to a specific failure mode. See Errors and error handling for the full code reference.
Webhooks
v3 webhooks are in active review
Webhook events, payload structure and signature verification have not changed between v2.6 and v3 to date, but ekko is reviewing the v3 webhook payload as part of finalising v3. Expect updates. The current spec below is accurate today.
Webhook events, payload structure and signature verification are identical between v2.6 and v3 today. The current spec is:
- Events:
IMPACT_PAYMENTandIMPACT_PAYMENT_REVERSAL - Payload fields:
footprintId,organisationId,brandId,productId,customerId,eventType,timestamp,payment,carbon - Signature: HMAC-SHA256 using your
organisationIdas the key, sent in thesignatureheader
If you already have v2.6 webhooks configured, no changes are required today. See Webhooks and Signature verification for the current spec.
Validating your migration
Before cutting over, run the v3 integration check Postman collections against the staging environment. The Direct API check runs 17 requests across organisation onboarding, impact partners, quotes, funds allocations, reversal and reconciliation. The Checkout SDK check runs 14 requests across the same areas plus session creation. Each request includes automated pass/fail assertions.
A full collection run takes under 30 seconds. See Test cases for setup and credentials.
Finding your organisationId
Your v3 organisationId is shown in ekko Hub. To find it:
- Log in to ekko Hub (hub.ekko.earth for production, staging.hub.ekko.earth for sandbox)
- Click Console in the left sidebar (under Developer)
- Your organisation ID is shown at the top of the page, above the API key list
For Postman, the integration check collections expect this value in the org_id environment variable. It must be your root organisation ID, not a child organisation ID.
New in v3
v3 introduces two additions worth knowing about as you migrate. Neither blocks your migration, and you can opt in once you're on v3.
- Location-matched projects: set
projectFilters.matchConsumerLocation: trueon quote and session requests to prioritise projects close to the consumer's location. The quote response includesconsumerLocationMatchAccuracyindicating match precision - Donation tax receipts: set
issueTaxReceipt: truewith aconsumerEmailonPOST /funds/allocationsto have ekko send a tax receipt to the consumer (US charity compliance)
