climatememory developers

API reference

Degree Days API

Heating and cooling degree days from ERA5-Land hourly reanalysis, back to 1950, anywhere on land — including the places with no weather station within 60 km. One scope — dju — covers this whole page.

Degree days#

GEThttps://api.climatememory.com/v1/degree-daysscope: dju2 credits, +1 per complete 365.25-day period in the span

Heating and cooling degree days for a coordinate.

Parameters

ParameterTypeDefaultDescription
latfloatRequired.
lonfloatRequired.
basenumber | preset | csv18Any base temperature in °C, or a preset: uk (15.5), ashrae (18.333), iso, france, eurostat. Up to 60 comma-separated, at no extra cost — see below.
methodstringhourlyhourly, costic, mean or eurostat. Your contract decides this, not us.
startISO date1 Jan of the end yearMaximum 10 years per request.
endISO datetoday
typestringbothHDD, CDD or both.
breakdownstringmonthlydaily, weekly, monthly or yearly.
elevationfloat (m)True ground height of your site. Shifts the series from the cell's height to yours — see below.
formatstringjsonjson or csv. CSV is a paid-plan feature.
{
  "location": {
    "latitude": 36.75, "longitude": 3.06,
    "grid_cell": { "latitude": 36.7, "longitude": 3.1,
                   "distance_km": 6.61, "resolution_km": 9 }
  },
  "parameters": {
    "base_temperature_c": 18.0, "method": "hourly",
    "start": "2025-07-01", "end": "2026-06-30"
  },
  "totals": { "hdd": 781.86, "cdd": 1331.61 },
  "quality": {
    "days": 365, "coverage": 1.0,
    "missing_hours": 0, "provisional_days": 0
  },
  "breakdown": [ { "year": 2025, "month": 7, "hdd": 0.0, "cdd": 289.4, "days": 31 } ]
}

The quality block is not decoration. coverage below 1.0 means hours were missing from the archive. provisional_days counts days filled from forecast rather than final reanalysis, because ERA5-Land publishes with about five days of latency.

If you are settling a contract on these numbers, check both. A total computed over 0.98 coverage is not wrong, but it is not the same claim as one over 1.0 — and the difference is invisible in totals.

What a degree day is, in one paragraph

A heating degree day measures how far below a base temperature the outside air sat, and for how long. At base 18 °C, an hour at 16 °C contributes (18 − 16) / 24 = 0.083 HDD. Sum the hours and you have a number proportional to the energy a building needed. Cooling degree days are the mirror: how far above the base. It is the standard way to compare one heating season with another after the weather has been taken out of the comparison.

Many bases, one request, one price#

GEThttps://api.climatememory.com/v1/degree-days?base=15,15.5,18,18.5,20scope: djusame as one base

Up to 60 base temperatures in a single call, at the price of one.

Reading and decoding the hourly series is the entire cost of a degree-days answer. Once that array is in memory another base is a subtraction over it, so asking for sixty costs what asking for one costs.

The response gains a by_base array carrying every base in the order you listed them. totals and breakdown keep describing the first one, so code written before this existed still works unchanged.

"by_base": [
  {"base": 15.0,  "hdd": 402.11, "cdd": 1731.4},
  {"base": 15.5,  "hdd": 431.02, "cdd": 1706.3},
  {"base": 18.0,  "hdd": 781.86, "cdd": 1331.6}
]

Useful when you do not yet know which base reproduces a contract's figures, or when the same building is settled at different bases by different parties — a landlord on 15.5 and an energy supplier on 18 are both right, and this returns both in one call.

Calculation methods#

GEThttps://api.climatememory.com/v1/degree-days/compare-methodsscope: dju2 credits, +1 per complete 365.25-day period in the span

All four methods over the same period, side by side.

GEThttps://api.climatememory.com/v1/methodsno key needed0 credits

The method definitions and their presets. No key needed.

The method is a parameter because your contract decides it, not us. The same data, same base, same year, at Algiers:

MethodHDD 18 °CCDD 18 °CWhen to use it
hourly781.91331.6Default. Integrates the hourly deficit — the most physically faithful.
costic741.71349.5French DJU unifiés. Required by French energy-performance contracts.
mean659.91267.8Daily mean against base. The most common international convention.
eurostat587.7656.6European statistics. Bases are fixed by the definition and ignore base.

Hourly and daily-mean differ by 18 % on the same data. That is not a rounding difference — it is the difference between winning and losing an argument about an energy bill. Pick the method that reproduces the figures in your contract before you commit to a plan; that is what compare-methods is for, and it costs one request.

eurostat ignores base entirely: the definition fixes its own thresholds, and honouring your parameter would produce a number that is not a Eurostat degree day while claiming to be one.

/v1/methods needs no key. Use it to populate a method picker in your UI without spending anything, and without hard-coding a list that will go stale.

Degree days by city#

GEThttps://api.climatememory.com/v1/degree-days/city/{country}/{slug}scope: dju2 credits, +1 per complete 365.25-day period in the span

The same, with the city's time zone and its urban heat island correction.

Applies two corrections a bare coordinate cannot: the city's time zone, so days are cut locally, and its calibrated urban heat island offset.

Reanalysis under-reads built-up areas by 1–3 °C. That biases cooling degree days low — material if you are sizing air conditioning, and invisible if you do not know to look for it.

elevation is not applied on this path, and the omission is deliberate. The city offset is already computed against stations normalised to the city's own elevation, so a second lapse correction would count the same altitude twice — in the same direction, and plausibly enough that nobody would notice. If you need a specific building's altitude, use the coordinate endpoint with elevation and forgo the heat-island correction.

Degree days for your building, not for the grid cell#

GEThttps://api.climatememory.com/v1/degree-days?elevation={metres}scope: djusame

Lapse-rate correction from the cell's ground height to yours.

A weather station is at the altitude it is at, and nobody can move it 600 m up the valley side for you. Our source is a model, so the cell's ground height is a number in the archive and the difference is arithmetic: 0.65 °C per 100 m. Over a heating season that is not a rounding error.

Opt-in, and the response says exactly what it did:

"elevation": {
  "applied": true,
  "model_elevation_m": 42.0,
  "location_elevation_m": 1200,
  "temperature_offset_c": -7.53,
  "caveat": "Lapse-rate correction. It assumes temperature falls smoothly with
             height, which a valley floor under a winter inversion does not."
}

The caveat travels in the response rather than only on this page, because the person reading the JSON six months from now is not the person who read the documentation.

Pinning a cell, so a baseline stays comparable#

GEThttps://api.climatememory.com/v1/cells/resolvescope: dju1 credit

Which cell would answer for a point, and how far away it is.

GEThttps://api.climatememory.com/v1/degree-days/cell/{cell_id}scope: dju2 credits, +1 per complete 365.25-day period in the span

Degree days for a named cell — no nearest-cell search, ever.

Every other location form re-runs the nearest-cell search on each request, so the answer depends on what the archive holds today. That is the right default for a one-off lookup, and the wrong one for a baseline: a multi-year comparison is only a comparison if each year comes from the same place.

As the archive widens, the nearest cell to a given site changes — an improvement in coverage that would otherwise arrive in your data as an unexplained step.

# once, at setup
GET /v1/cells/resolve?lat=45.19&lon=5.72
  → { "id": "era5l_45.20_5.70", "distance_km": 1.4, "resolution_km": 9 }

# every time after that
GET /v1/degree-days/cell/era5l_45.20_5.70?base=18&start=2015-01-01&end=2025-12-31

distance_km is 0 on a pinned request by construction: you named the cell, so nothing was substituted for it. A cell that no longer exists returns 404 cell_not_found rather than quietly falling back to a neighbour — which would reintroduce the very substitution you pinned it to avoid.

/v1/cells/resolve is also the cheap way to find out before you pay for data that the nearest cell is 60 km away. It decodes nothing and reads no time series, and is priced accordingly at one credit.

Breakdown periods#

GEThttps://api.climatememory.com/v1/degree-days?breakdown={daily|weekly|monthly|yearly}scope: djusame

Aggregate the same request to the period your contract settles on.

Every degree-days response carries a breakdown array aggregated to the period you ask for. Contracts settle on different ones, so all four are available on the same request at the same price.

PeriodEach row carries
dailydate, HDD, CDD, min/max/mean temperature
weeklyISO year and week, the date it starts, totals
monthlyyear, month, totals — the default
yearlyyear, totals

Weeks are ISO weeks, so a week belongs to the year containing its Thursday. 1 January 2023 falls in week 52 of 2022, and we report it there — which is what your spreadsheet will do too, and disagreeing with the spreadsheet is how a reconciliation meeting starts.

Every bucket also carries days, so a partial month at the edge of your range is visible rather than silently short. A February with "days": 12 is a February you should not compare against a full one.

Monthly totals for a year#

GEThttps://api.climatememory.com/v1/degree-days/monthlyscope: dju2 credits

Twelve monthly totals for one calendar year, with no daily series.

GEThttps://api.climatememory.com/v1/coverageno key needed0 credits

What the archive holds: first and last day with data, and the axis it will grow into. No key needed.

A shorthand for the common case: ?lat=&lon=&year=2025 and twelve rows come back. Same data as /v1/degree-days?breakdown=monthly over the same span; fewer parameters to get wrong.

/v1/coverage reports the archive's first and last day and its resolution, needs no key, and is the right thing to check before you ask for a period near the present edge — ERA5-Land runs about five days behind, and provisional_days in your response is the consequence.

{
  "start": "1950-01-01",
  "end": "2026-07-29",        // last day that HOLDS data
  "axis_end": "2026-12-31",   // where this run will stop growing
  "hours": 671256, "axis_hours": 674976,
  "cells": 86274, "resolution_km": 9.0,
  "run": "world-1950-2026-p1000"
}

end and axis_end are different questions, and only the first is about data. A run is written against the whole calendar it will eventually fill — the 1950-2026 archive allocates every hour to 31 December 2026 — and the top-up fills it as Copernicus publishes.

Until 2026-08-03 this endpoint reported the axis as end, so it credited the archive with roughly five months of hours that were empty. end is what you can ask for today; a request entirely past it is a 404 outside_archive rather than a well-formed answer totalling zero.

CSV export#

GEThttps://api.climatememory.com/v1/degree-days?format=csvscope: dju4× the JSON call

The breakdown rows as a CSV file. Paid plans only.

Add format=csv to any degree-days request. You get the breakdown rows as a CSV file, named after the location and dates so it is still identifiable six months later in a downloads folder.

year,month,hdd,cdd,days,t_mean,coverage,provisional
2024,1,205.03,2.88,31,11.48,1.0,false
2024,2,168.44,4.10,29,12.31,1.0,false

Paid plans only, and bounded: the same maximum span as a JSON request, aggregated rows only — never the hourly series — and it costs four credits for every one the equivalent JSON call costs. A free key gets 402 export_not_in_plan.

That is deliberate rather than grudging. The archive is what you are paying for, and an unbounded export is how a competitor acquires it in an afternoon. The bounds are what let the format exist at all.

Hourly history#

GEThttps://api.climatememory.com/v1/historicalscope: dju(years + 1) × (variables ÷ 2), rounded down, min 1

The reanalysis archive served raw: hourly weather back to 1950.

Parameters

ParameterTypeDefaultDescription
latfloatRequired.
lonfloatRequired.
startISO dateThe span of one request is capped — see Plans.
endISO date
variablescsva sensible subsetAsk /v1/historical/variables what this archive holds.
hourlybooltrueInclude the hour-by-hour series.
dailyboolfalsetrue for citiesInclude daily aggregates, cut on local calendar days.

The same archive the degree days are built from, served directly: hourly, back to 1950, on a 9 km grid, everywhere on land. Same host and same dju scope — if you can call degree days, you can call this.

Today this archive holds temperature and nothing else. It was ingested for degree days, and degree days need one variable. This page promised "humidity, wind, precipitation and solar radiation" until 2026-08-03 and the archive has never held any of them.

/v1/historical/variables is the answer that is always current — it reads the promoted run rather than this sentence, needs no key, and costs nothing. Call it before you build against a field.

What makes it worth paying for is consistency. A weather station record carries every move, every instrument change and every gap in its history, so a thirty-year trend computed from one is partly a trend in the instrumentation. A reanalysis has none of that: the model is the same model for every year in the record.

For daily values over a long period, or for normals and trends, use the Climate API instead — it holds the daily fields pre-derived, has no 9 km/1950 boundary, and answers a whole climatology in one call.

History by city#

GEThttps://api.climatememory.com/v1/historical/city/{country}/{slug}scope: dju(years + 1) × (variables ÷ 2), rounded down, min 1

Identical, with the city's time zone and heat-island correction.

Two things a coordinate cannot carry: the city's time zone, so days are cut where the city actually experiences them, and its calibrated urban heat island correction. daily defaults to true here, because a city request is almost always a request about days.

Available variables#

GEThttps://api.climatememory.com/v1/historical/variablesno key needed0 credits

What the archive currently holds, with units, and which fields are derived. No key needed.

Lists what the archive holds right now, with units, and which fields are derived rather than stored. An archive ingested for degree days alone holds temperature only, and this endpoint says so plainly instead of returning columns of null.

Some fields are computed rather than stored: humidity from dew point, wind speed and direction from the u and v components. Storing what takes microseconds to calculate would add a third to the archive for nothing — but a derived field only appears when its sources are in the run, which is why this endpoint, and not a list written on a page, is the truth about what you can ask for. On the archive promoted today the sources are absent, so the answer is temperature_2m alone.