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.

Areav2.6v3
AuthenticationX-Api-Key headerAuthorization: Bearer
Base URLapi.ekko.earth/v2.6api.ekko.earth/v3
Organisation identifierproductIdorganisationId
Consumer identifiercustomerId (string)consumer object
Transaction amountamount.valuetransaction.amount
Credits fieldcompensationcredits
Address fieldsstreet, countryline1, countryCode
Organisation onboardingTwo endpoints, brand/product modelSingle POST /organisations, nested hierarchies
Footprint calculationPOST /impact_calculatorPOST /quotes/carbon
Quote referencecalcReferencequoteId
Funds allocationPOST /fundsPOST /funds/allocations
Funds referencereferencefundsAllocationId
ReversalsPOST /reversal (body param)POST /funds/allocations/{id}/reverse (path param)
ReconciliationPOST /impact_recordsGET /funds/allocations/impact and GET /funds/allocations/profit-share
ProjectsGET /products/{productId}/projectsGET /impact-partners/projects
SDK sessionscheckoutSessionId, impactPaySessionIdsessionId, quoteId, expiresAt
Success status codes200 for all POSTs201 for all POSTs
Error envelopeNested error object with statusCode, message, errorName, details, path, requestId, timestampFlat {error, code}
WebhooksUnchanged todayIn 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 compensationcredits, 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_here

v3 uses a standard Bearer token in the Authorization header:

Authorization: Bearer your_api_key_here

Update 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.

EnvironmentURL
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 (and address1/address2/address3 in onboarding requests) becomes line1, line2, line3
  • country becomes countryCode and 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 details
  • POST /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 mcc on the organisation lets you omit the merchant object from quote and SDK session requests.

Field mapping from v2.6

v2.6 fieldv3 field
tradingNametradingName
legalNamelegalName
currencycurrencyCode
billingEmailbilling.email
companyRegistrationNumberbilling.companyRegistrationNumber
vatNumberbilling.taxNumber
brandReferenceorganisationReference
brandOwnerSet parentOrganisationId to the owner organisation's ID
brandBilling: trueInclude a billing object with required billing fields
brandBilling: falseOmit the billing object; the nearest billable parent is invoiced
tradingAddress and legalAddressSingle 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:

  • footprint contains co2eGrams, co2eOunces and localised equivalents
  • credits contains pricing (totalAmount, impactAmount, impactSalesTaxAmount, serviceFeeAmount, serviceFeeSalesTaxAmount) and impact partner details
  • contribution contains 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 projectIds from credits or impactPartnerIds from contribution, 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:

  1. Identifier: v2.6 returned {acknowledged, reference, compensation, contribution}. v3 returns fundsAllocationId as the primary identifier. The acknowledged boolean is removed.
  2. Granular breakdown: v3 returns credits and contributions arrays with per-line entries, each containing the impact amount, service fee and granular sales tax fields (impactSalesTaxAmount, impactSalesTaxRate, serviceFeeSalesTaxAmount, serviceFeeSalesTaxRate).
  3. Tax liability: a new top-level taxLiability field (platform or organisation) 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/reverse

Response changes

v2.6 returned {acknowledged, reference, message}. v3 returns the same shape as a funds allocation response, with:

  • All amounts negated
  • The original fundsAllocationId preserved
  • A new reversalId field 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: productIdorganisationId, customerId (string) → consumer object, amounttransaction.

Response changes

v2.6 fieldv3 field
checkoutSessionIdsessionId
calcReferencequoteId
clientSecretclientSecret (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 fieldv3 field
impactPaySessionIdquoteId
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/sessions endpoint 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.6v3
POST /impact_records with filters in bodyGET /funds/allocations/impact with filters as query parameters
POST /impact_records/transactionGET /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-7b8a0db1a2c3

v3 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 fieldv3 equivalent
transactionReferenceIdfundsAllocationId
transactionDatefundsDateTime
brandId and brandTradingNamefundsOrganisationId and fundsOrganisationReference
customerIdconsumerReference
impactAmountamounts.impact
serviceFeeamounts.serviceFee
clientServiceFeeShare and ekkoServiceFeeShareAvailable 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.
recordTypeReplaced by separate credits and contribution arrays on each record

v3 records also add:

  • An allocation-level amounts object (total, impact, impactTax, serviceFee, serviceFeeTax) that mirrors the quote response, so you can compare actuals against the original quote using quoteId
  • unit per line item (e.g. kgCO2e, bottles) with a quantity
  • impactPartnerId per line item
  • A taxLiability field (platform or organisation) indicating sales tax responsibility. This is a new concept, not a rename of fundsFlow

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 endpointPurpose
GET /impact-partners/projectsList projects across all impact partners
GET /impact-partnersList 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_credits or contribution
  • theme: pollution, climate_stress, land_use or water_use
  • location: countryCode, region and state
  • sdgs: array of associated UN Sustainable Development Goals
  • subtype: for carbon credit projects (e.g. sequestration, carbon removal)
  • shortDescription and longDescription as separate fields
  • impactPartnerId linking 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.requestId or error.timestamp must be updated.
  • code is 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 example MAX_KEYS_REACHED instead of CONFLICT) 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_PAYMENT and IMPACT_PAYMENT_REVERSAL
  • Payload fields: footprintId, organisationId, brandId, productId, customerId, eventType, timestamp, payment, carbon
  • Signature: HMAC-SHA256 using your organisationId as the key, sent in the signature header

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:

  1. Log in to ekko Hub (hub.ekko.earth for production, staging.hub.ekko.earth for sandbox)
  2. Click Console in the left sidebar (under Developer)
  3. 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: true on quote and session requests to prioritise projects close to the consumer's location. The quote response includes consumerLocationMatchAccuracy indicating match precision
  • Donation tax receipts: set issueTaxReceipt: true with a consumerEmail on POST /funds/allocations to have ekko send a tax receipt to the consumer (US charity compliance)