Ten reservoir gauges, four ways to say “water level”
What a live reservoir feed actually returns, checked against the USGS site service and series catalog on 2026-08-09. A permanent copy.
A reservoir level page looks like it should be a thin wrapper over a public API. Pick a lake, read the gauge, divide by full pool, print a percentage. The part that is genuinely hard is none of that. It is that water level is not one field, storage is not one unit, and percent full is not one number. This note works through all three on a concrete set of ten gauges, and re-verifies the reservoir→gauge crosswalk they came from while it is at it.
The dataset and the check
The starting point is a reference table of 44 US reservoirs carrying, for each one, the managing operator, a published full-pool elevation, a capacity in acre-feet where one is recorded, and the identifier of a live feed. Full-pool elevation is present for 44 of 44; capacity for only 16. That gap matters later.
The live-feed column splits like this:
| Feed | Reservoirs | Share |
| no live feed recorded | 19 | 43.2% |
| California CDEC | 12 | 27.3% |
| USGS NWIS | 10 | 22.7% |
| USBR | 3 | 6.8% |
Three different agencies, three different APIs, three different response shapes, and 19 reservoirs with no machine-readable feed recorded at all. The 44 reservoirs are managed by 21 distinct operator entities. There is no single upstream to integrate against; there is a federation, and the crosswalk from a lake name to the right identifier at the right agency is the actual asset.
The crosswalk resolves, 10 of 10
Every USGS site number in the table was re-queried against the NWIS site service in a single request:
curl -s "https://waterservices.usgs.gov/nwis/site/?format=rdb&siteOutput=expanded&sites=<comma-separated ids>"
10 of 10 resolved, each to a station whose name matches the reservoir it is filed under. That is the good news, and it is worth stating plainly because NWIS holds on the order of two million sites and a wrong site number does not fail loudly — it returns a real number for a different body of water.
| Reservoir | Site | NWIS station name | Datum | alt_va |
| Lake Buchanan | 08148000 | LCRA Lk Buchanan nr Burnet, TX / Lk Buchanan nr Burnet, TX | NAVD88, NGVD29 | 0, 0.48 |
| Medina Lake | 08179500 | Medina Lk nr San Antonio, TX | NGVD29 | 0.0 |
| Canyon Lake | 08167700 | Canyon Lk nr New Braunfels, TX | NGVD29 | 0 |
| Lake Belton | 08102000 | Belton Lk nr Belton, TX | NGVD29 | 0 |
| Lake Somerville | 08109900 | Somerville Lk nr Somerville, TX | NGVD29 | 0 |
| Lake Fork Reservoir | 08018800 | Lake Fk Res nr Quitman, TX | NGVD29 | 0 |
| Lake Whitney | 08092500 | Whitney Lk nr Whitney, TX | NGVD29 | 0 |
| Lake Powell | 09379900 | LAKE POWELL AT GLEN CANYON DAM, AZ | NGVD29 | 3718.82 |
| Lake Allatoona | 02393500 | ALLATOONA LAKE NEAR CARTERSVILLE, GA | NAVD88 | 0.18 |
| Nolin River Lake | 03310900 | NOLIN LAKE NEAR KYROCK, KY | — | — |
You cannot check a reservoir gauge ID by its elevation
The obvious automated sanity check is to compare the site record’s alt_va against the published full-pool elevation and flag anything wildly off. It does not work. Of the 10 gauges, 8 report an altitude within a foot of zero, and one reports nothing at all. alt_va on a lake gauge is the altitude of the gauge datum or the land surface at the installation, not the water. For most of these it has simply been left at zero, which is a legitimate encoding for a gauge whose readings are published as absolute elevations rather than as heights above a local datum.
The one gauge that does carry a real altitude illustrates the same point from the other side. Lake Powell (09379900) reports alt_va = 3718.82 ft NGVD29, against a published full-pool elevation of 3700 ft — 18.82 ft above full pool. Nothing is wrong. The gauge datum is not the pool. An automated check that expected these to agree would have raised a false alarm on the one gauge that populated the field honestly.
The only check that actually worked was string-matching the NWIS station name against the reservoir name, by eye. Ten times. That is the honest cost of a crosswalk like this, and it is why the crosswalk is worth keeping once it exists.
Four ways to say water level
Asking each gauge what time-series it actually publishes:
curl -s "https://waterservices.usgs.gov/nwis/site/?format=rdb&seriesCatalogOutput=true&sites=<ids>"
Across ten reservoir gauges there are 4 distinct parameter codes carrying a water-surface level, and they are not interchangeable:
| Code | USGS name | Unit | Gauges |
| 00062 | Elevation of reservoir water surface above datum, feet | ft | 2 |
| 00065 | Gage height, feet | ft | 1 |
| 62614 | Lake or reservoir water surface elevation above NGVD 1929, feet | ft | 8 |
| 62615 | Lake or reservoir water surface elevation above NAVD 1988, feet | ft | 2 |
Two of those are absolute elevations on two different vertical datums. One is an elevation above an unspecified local datum — the datum lives in the site record, not in the parameter. One is a gage height, which is a depth above a gauge zero and is not an elevation at all. A client that reads only 62614, the most common of the four, silently returns nothing for 2 of these 10 reservoirs.
| Reservoir | Level codes | Storage codes |
| Lake Buchanan | 00062, 62615 | — |
| Medina Lake | 62614 | 00053, 00054 |
| Canyon Lake | 62614 | 00054 |
| Lake Belton | 62614 | 00054 |
| Lake Somerville | 62614 | 00054 |
| Lake Fork Reservoir | 62614 | 00054 |
| Lake Whitney | 62614 | 00054 |
| Lake Powell | 62614, 62615 | — |
| Lake Allatoona | 00062 | 72036 |
| Nolin River Lake | 00065, 62614 | — |
The factor of a thousand
The storage column above is where a bug would actually cost something. Two of the codes in use both mean reservoir storage:
| Code | USGS name | Unit | Gauges |
| 00053 | Surface area, acres | ac | 1 |
| 00054 | Reservoir storage, acre-feet | ac-ft | 6 |
| 72036 | Reservoir storage, thousand acre feet | Kac-ft | 1 |
00054 is acre-feet. 72036 is thousand acre-feet. In this sample 6 gauges publish the first and 1 publishes the second. A percent-full calculation that divides either one by a capacity in acre-feet without checking the code is wrong by three orders of magnitude on the 1 that uses 72036 — and wrong in the direction that looks plausible rather than absurd, because a reservoir reported at 0.06% full reads as a drought story rather than as a unit error.
Separately, 3 of the 10 gauges publish no storage series at all. For those, storage is not something you read; it is something you infer from elevation, which brings us to the part that has no clean answer.
Two vertical datums, and one gauge with both
Across the ten site records the datum field takes 2 values: NGVD29 (8), NAVD88 (2). NGVD29 and NAVD88 are different vertical reference surfaces, and the offset between them varies by location. Two reservoir elevations quoted on different datums are not directly comparable, and the difference is not a constant you can hard-code.
Lake Buchanan makes the point sharper. Site 08148000 comes back as 2 records from TX071 and USGS, filed under LCRA Lk Buchanan nr Burnet, TX and Lk Buchanan nr Burnet, TX, on NAVD88 and NGVD29 respectively, with alt_va of 0 and 0.48. One site number, two agency records, two vertical datums. Deduplicating that response by site number and keeping whichever row arrives first is a coin flip over which datum you end up on.
Percent full is two different numbers
Here is the part that is a modelling problem rather than a plumbing problem. A reservoir basin is roughly a valley: its surface area grows as the water rises. Write the storage above dead pool as a power of the depth,
S(h) = S_full * ((h - h_dead) / (h_full - h_dead)) ** b, b > 1
with b = 1 a vertical-walled tank, b = 2 a V-shaped valley of constant side slope, and b = 3 a cone. Then a gauge sitting at fraction f of the depth range holds fb of the volume:
| Depth-based “percent full” | b=1.5 | b=2.0 | b=2.5 | b=3.0 |
| 90% | 85.4% | 81.0% | 76.8% | 72.9% |
| 75% | 65.0% | 56.2% | 48.7% | 42.2% |
| 50% | 35.4% | 25.0% | 17.7% | 12.5% |
| 25% | 12.5% | 6.2% | 3.1% | 1.6% |
Because f < 1 and b > 1, fb < f always. That is a directional result, not a guess: a percent-full figure computed from elevation alone is systematically higher than the storage-based figure the operating agency publishes, and the gap widens as the reservoir empties. At half depth in a V-shaped basin the honest storage number is 25%, not 50%. Running it backwards, the depth you need in order to actually hold a given share of the volume:
| Storage share | b=1.5 | b=2.0 | b=2.5 | b=3.0 |
| 50% of volume | 63.0% of depth | 70.7% of depth | 75.8% of depth | 79.4% of depth |
| 25% of volume | 39.7% of depth | 50.0% of depth | 57.4% of depth | 63.0% of depth |
Be clear about what that table is. The exponent b is a free parameter. It is not in the reference dataset and it cannot be derived from it, because a full-pool elevation and a total capacity are two numbers and the shape of the basin between them is a curve. Operating agencies publish that curve as an area-capacity table, and the honest way to convert an elevation to a storage is to interpolate the real table for that specific reservoir. The model above is here to show the size and the sign of the error you take by not doing so, not to substitute for it. And for 28 of the 44 reservoirs in this dataset the question does not even arise: no capacity figure is recorded, so a volumetric percent full is not computable from it at any accuracy.
The units that are exact
One small consolation: the volume conversions are definitional, so they can be derived rather than looked up, and they are exact.
1 international foot = 0.3048 m (exact)
1 acre = 43560 sq ft (exact)
= 43560 * 0.3048^2
= 4046.8564224 m^2 (exact)
1 acre-foot = 4046.8564224 * 0.3048
= 1233.48183754752 m^3 (exact)
So the largest capacity recorded here, Lake Mead at 28,945,000 acre-feet, is 35.70 cubic kilometres, and it is 181 times the smallest recorded capacity, Lake Somerville at 160,000 acre-feet. The 16 reservoirs with a recorded capacity total 51,159,262 acre-feet, or 63.1 cubic kilometres. Those are the operators’ own conservation-pool figures carried through an exact unit conversion; the conversion adds no error, and the figures are only as good as the operator’s published pool definition, which does change when a dam is resurveyed or reoperated.
What this adds up to
- Resolve the gauge by name, not by elevation. The elevation field on a lake gauge is usually zero and is never the water surface.
- Read the parameter code before reading the value. Four codes mean level here and two mean storage, and one of the two storage codes is in thousands.
- Carry the vertical datum alongside every elevation, and refuse to compare two elevations on different datums. One site number in this sample returns two datums from two agencies.
- Say which percent full you mean. If it is derived from elevation without an area-capacity curve, it is biased high, and by how much depends on a basin shape you have not measured.
- Expect to fall back to a human. 19 of 44 reservoirs here have no machine feed recorded at all.