Central Valley Surfacewater Map
Public API
Read-only files of what the map shows: 924 measured series of canals, rivers, turnouts, reservoirs and the Delta at daily, weekly, monthly and water-year steps, the system ledgers, the tributary flow requirement verdicts and the data notes. Static JSON and CSV in a public bucket: no key, no sign-up, no rate limit of ours.
https://storage.googleapis.com/surfacewater-map-509303/api/v1/Endpoints
/api/v1/index.json– This list as JSON: every v1 endpoint, the licence, the data period and every series./api/v1/release.json– The release these files are, where its frozen copy is and how to cite it./api/v1/releases.json– Every release of the site and where its frozen copy is./api/v1/series.csv– Every series as one table, one row each (the catalog of index.json)./api/v1/series/<id>.json– One series at every step: daily, weekly, monthly and water-year values with a quality per value./api/v1/series/<id>.csv– One series as a long table: every step, one row per period with a value./api/v1/sources.json– The published series the map is built from: gauges, plants and report tables./api/v1/accounting/index.json– The system ledgers: which systems have one./api/v1/accounting/<system>.json– A system's annual ledger: per reach and water or calendar year, the measured terms and the unmeasured gain / loss./api/v1/accounting/<system>.csv– The annual ledger as a table, one row per reach and period (sub-reaches left out)./api/v1/accounting/<system>_monthly.json– A system's ledger by month: the same terms per reach and month./api/v1/accounting/<system>_monthly.csv– The monthly ledger as a table, one row per reach and month./api/v1/estimates/index.json– Estimates, not measurements: loss and open-term ranges, in their own files./api/v1/tributaries/index.json– Tributary flow requirements: the rivers, compliance points, instruments and requirements the Tributaries Map judges./api/v1/tributaries/<river>.csv– One river: per judged requirement and month, the day counts behind the verdict./api/v1/notes.json– The data notes: every source defect and pipeline fix found, with evidence, resolution and live status.
Measured, estimated, not publicly measured
- Every value in series/ and accounting/ is tied to a published measurement: a gauge, a pumping plant, a delivery table or report. No gap is filled with a modelled number.
- Each value carries a quality:
measured(1);estimated(2): the day's value uses a term the map flags~(an approximation, e.g. a nearby gauge standing in) or?(an optional term that was missing that day and counted as 0);monthly record(3): a month from a published monthly delivery table where there is no daily record (measured, but monthly). - Not publicly measured: 29 lines are drawn on the map although no agency publishes their flow. They are listed in the catalog with
status: "not publicly measured"and have no value files. A blank ornullis no record, never zero. - Ledger rows have a quality too:
measured,estimated(a term is an approximation),incomplete(a term is missing: no gain / loss is given) orsuspect. - Estimates (evaporation, seepage and open-term ranges) are modelled. They are only under
/api/v1/estimates/, each file sayslayer: "estimate", and none is added into a series, a ledger or a total. The map's forecast layer is not in the API. - Data notes (
notes.json) list every source defect and pipeline fix found; each series and requirement names the notes about it.
Conventions
- Flows are cfs, the mean over the period (1 cfs for one day = 1.98347 acre-feet). Reservoir storage is acre-feet on the last day of the period. Ledgers are acre-feet.
- Steps:
day;weekMonday to Sunday, labelled by its Monday;monthYYYY-MM;wywater yearWYyyyy, October of the year before to September. - A series' lists line up:
values[i]andquality[i]belong toperiods[i](daily: the daystart+ i). - CSV files are UTF-8, comma-separated, with one header row and no comment lines.
- Values are historical published records; provisional data are subject to revision by the agencies, and a later release may differ.
Versioning
- The fields listed here are frozen in v1: their name, units and meaning won't change.
- v1 only grows: new fields, new series, new endpoints. A breaking change goes to
/api/v2, and v1 stays up beside it. - Fields not listed here are the map's own and can change. Don't rely on them.
Releases
Each deploy is a release, named by its time (UTC), and the site is frozen at snapshots/<release>/, the API files with it. A frozen copy never changes, so a number quoted from it can be checked later. The monthly data refresh is the monthly data release. /api/v1/release.json names the release you are reading and its frozen copy (api_snapshot). The per-series files are frozen only when they change (about once a month, with the data refresh), so a release's series files can sit in an earlier release's copy: series_snapshot says where. /api/v1/releases.json lists them all.
Use from code
import pandas as pd
sha_storage = pd.read_csv("https://storage.googleapis.com/surfacewater-map-509303/api/v1/series/SHA.storage.csv")
monthly = sha_storage[sha_storage.step == "month"]Files are served gzip-compressed to clients that ask for it (curl --compressed; browsers and pandas do). Browser pages on other sites can fetch them (CORS: GET from any origin).
Licence and citation
The series, ledgers, verdicts and notes as compiled here are licensed CC BY 4.0. Cite the release you used:
Gray, W. (2026). Central Valley Surfacewater Map, release <release>. CC BY 4.0.
The source data keep their own terms: DWR (CDEC, Dayflow, Water Data Library, SWP reports, Bulletin 132); Reclamation (CVO reports); USGS (NWIS); U.S. Army Corps of Engineers (CWMS); State Water Resources Control Board; FERC; NMFS / USFWS (biological opinions). sources.json lists every source series.
The map’s line geometry (data/network.geojson, not part of this API) is built from OpenStreetMap ways and USGS NHD and is offered under the Open Database License 1.0, © OpenStreetMap contributors, not CC BY.
Cost and limits
The files sit in a Google Cloud Storage bucket (us-west1), inside its Always Free tier (5 GB-months of storage, 100 GB a month of transfer to North America, 50,000 reads a month); beyond it about $0.02 per GB-month stored, $0.12 per GB transferred and $0.0004 per 1,000 reads (prices read 2026-10-21 from cloud.google.com/storage/pricing). There is no rate limit of ours, but please take the bulk you need once rather than polling: the data change about once a month.
GET /api/v1/index.jsonJSON
This list as JSON: every v1 endpoint, the licence, the data period and every series.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/index.json"
| Field | Type, units | Meaning |
|---|---|---|
version | string | "v1". |
licence | string | "CC BY 4.0". |
licence_url | string | URL of the licence. |
cite | string | Citation line without a release id; release.json has the line with it. |
docs | string | URL of the docs page. |
data.start | YYYY-MM-DD | First day of the record. |
data.end | YYYY-MM-DD | Last day with data. |
data.built | YYYY-MM-DD HH:MM | When the map's data were built (Pacific time). |
quality_codes | object | 1 = measured, 2 = estimated (an approximation flagged ~ or ? in the map's network, or a stand-in series), 3 = monthly record (a published monthly table, measured); null = no record (never zero). |
endpoints[] | object | name, path, summary, example, format and fields (the frozen names). |
series[] | object | Every series, with the fields listed under series.csv. |
counts | object | Number of series by status. |
GET /api/v1/release.jsonJSON
The release these files are, where its frozen copy is and how to cite it.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/release.json"
| Field | Type, units | Meaning |
|---|---|---|
release | string | The deploy this copy came with (UTC time, e.g. 2026-11-06T1530). |
api_release | string | null | The release whose frozen copy holds these API files except series/ (the same as release; null when the live API has no frozen copy). |
api_snapshot | string | null | URL of that frozen copy's api/v1/. |
series_release | string | null | The release whose frozen copy holds these exact series/ files: series files are frozen only when they change, so it can be an earlier release. |
series_snapshot | string | null | URL of that frozen copy's api/v1/series/ (in a frozen copy, index.json's series file paths are relative to it). |
built | YYYY-MM-DD HH:MM | When the data were built. |
data_end | YYYY-MM-DD | Last day with data. |
cite | string | Citation line with api_release. |
Written at deploy time. A local build carries the last deploy's record.
A frozen copy (snapshots/<release>/api/v1/) holds every file but series/ when its series files are the same as an earlier release's; series_snapshot says where they are.
GET /api/v1/releases.jsonJSON
Every release of the site and where its frozen copy is.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/releases.json"
| Field | Type, units | Meaning |
|---|---|---|
[].release | string | Release id. |
[].built | string | When its data were built. |
[].data_start | YYYY-MM-DD | First day of its record. |
[].data_end | YYYY-MM-DD | Last day with data. |
[].deployed | string | When it went live (UTC). |
[].path | string | Its frozen site: snapshots/<release>/index.html. |
[].api | string | null | Its frozen API: snapshots/<release>/api/v1/; null before the API existed. |
[].series | string | null | Where its series files are frozen: snapshots/<release>/api/v1/series/, an earlier release's when the series did not change; null before the API existed. |
GET /api/v1/series.csvCSV
Every series as one table, one row each (the catalog of index.json).
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/series.csv"
| Column | Type, units | Meaning |
|---|---|---|
id | string | Series id: a map line (e.g. swp_nba), a reservoir role (SHA.storage, SHA.inflow, SHA.release) or a Delta term (DELTA.inflow.Yolo_Bypass). |
name | string | Name as on the map. |
group | string | line, reservoir or delta. |
kind | string | canal, river, turnout, checkpoint, delivery, tunnel, pump; reservoir_storage, reservoir_inflow, reservoir_release; delta_inflow, delta_exports, delta_use, delta_outflow, delta_channels. |
system | string | SWP, CVP, Friant, Delta, River or Local. |
map | string | delivery (the Delivery Map) or tributaries (drawn on the Tributaries Map only). |
unit | string | cfs (a flow: the mean over the period) or af (reservoir storage: the last day of the period). |
aggregation | string | mean or end_of_period. |
status | string | measured, not publicly measured (drawn on the map, no published measurement; no value files) or no record (a published source exists but holds no value in the period). |
approximation | bool | Some values come from an expression flagged ~ or ?: those values carry quality 2 (estimated). |
daily_record | string | How a day's value is made: published series ids added or subtracted, tried in order ("|" = else). Empty when there is none. |
monthly_record | string | The published monthly table a month's value comes from where there is no daily record. Empty when there is none. |
sources | string[] | Ids of the published source series used (see sources.json). |
data_notes | string[] | Ids of the data notes about this series (see notes.json). |
steps.<step> | object | For each step with a value (day, week, month, wy): first, last (periods) and n (values). |
files | object | json and csv: paths of the series' files, relative to api/v1/. Absent when the status is not measured. |
note | string | null | The map's note on the line. |
Lists are joined with spaces; steps.<step> becomes <step>_first, <step>_last and <step>_n columns; files becomes json and csv columns.
GET /api/v1/series/<id>.jsonJSON
One series at every step: daily, weekly, monthly and water-year values with a quality per value.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/series/SHA.storage.json"
| Field | Type, units | Meaning |
|---|---|---|
id | string | Series id. |
name | string | Name. |
unit | string | cfs or af. |
aggregation | string | mean or end_of_period. |
quality_codes | object | 1 = measured, 2 = estimated (an approximation flagged ~ or ? in the map's network, or a stand-in series), 3 = monthly record (a published monthly table, measured); null = no record (never zero). |
steps.day.start | YYYY-MM-DD | First day; values run one per day from it. |
steps.<step>.periods | string[] | week: the Monday the week starts (YYYY-MM-DD); month: YYYY-MM; wy: WYyyyy (October of the year before to September). |
steps.<step>.values | number | null [] | One per day or period; null = no record. |
steps.<step>.quality | int | null [] | One per value, codes as in quality_codes. |
licence | string | "CC BY 4.0". |
cite | string | Citation line (see release.json for the release id). |
A week, month or water year has a value only when enough days have one (weekly 5 of 7, monthly 20 days, water year 330 days for flows; storage needs a reading in the last week). A monthly-only delivery line has month and water-year values only.
Values are rounded: daily and weekly to 1 cfs, monthly and water-year means to 0.1 cfs.
GET /api/v1/series/<id>.csvCSV
One series as a long table: every step, one row per period with a value.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/series/swp_nba.csv"
| Column | Type, units | Meaning |
|---|---|---|
step | string | day, week, month or wy. |
period | string | As in the JSON: the day (YYYY-MM-DD), the Monday a week starts (YYYY-MM-DD), the month (YYYY-MM) or the water year (WYyyyy, October of the year before to September). |
value | number | The value; rows without one are left out. |
unit | string | cfs or af. |
quality | string | measured, estimated or monthly record. |
GET /api/v1/sources.jsonJSON
The published series the map is built from: gauges, plants and report tables.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/sources.json"
| Field | Type, units | Meaning |
|---|---|---|
[].series_id | string | Source series id, as used in daily_record. |
[].source | string | cdec, cdec_h, usgs, dayflow, cvo_dout, swpops, wdl, cwms, ... (agency and system). |
[].station | string | Station or site code at that source. |
[].param | string | Sensor or parameter code. |
[].kind | string | flow or storage. |
[].name | string | Name. |
[].notes | string | Notes on the series. |
GET /api/v1/accounting/index.jsonJSON
The system ledgers: which systems have one.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/accounting/index.json"
| Field | Type, units | Meaning |
|---|---|---|
[].system | string | swp, cvp, friant, sac: the <system> of the ledger paths. |
[].name | string | Name. |
[].short | string | Short name. |
[].estimates | bool | Whether estimates/ holds loss estimates for it. |
GET /api/v1/accounting/<system>.jsonJSON
A system's annual ledger: per reach and water or calendar year, the measured terms and the unmeasured gain / loss.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/accounting/swp.json"
| Field | Type, units | Meaning |
|---|---|---|
system | string | System id. |
name | string | Name. |
unit | string | "af" (acre-feet per period). |
source | string | Where the numbers come from. |
incomplete | string | What the record lacks. |
reaches[] | object | id, name, note, gaps and the other descriptions of each reach. |
annual[].reach | string | Reach id. |
annual[].period | string | WYyyyy or CYyyyy. |
annual[].basis | string | daily, monthly or b132 (DWR Bulletin 132). |
annual[].head | af | null | Flow measured into the reach at its head. |
annual[].inflow | af | null | Measured inflows along the reach. |
annual[].delivery | af | null | Measured deliveries (turnouts, diversions). |
annual[].branch | af | null | Measured branch and spill outflows. |
annual[].storage_change | af | null | Change in the storage of reservoirs or pools on the reach. |
annual[].tail | af | null | Flow measured at the reach's end. |
annual[].residual_total | af | null | Unmeasured gain (+) or loss (−): what the measured terms leave unexplained (evaporation, seepage, unlisted users, ungauged inflow, measurement error). null when a term is missing. |
annual[].operator_loss | af | null | The operator's own booked losses, as printed, for comparison (negative = loss). |
annual[].quality | string | measured, estimated (a term is an approximation), incomplete (a term is missing) or suspect. |
annual[].subs[] | object | Sub-reaches: from, to, residual (af). |
Rows carry only the terms that apply to the reach; other measured terms (inflow_unlisted, delivery_federal, residual_cdec, points_dwr, combined) are described in the reach notes.
Nothing in a ledger is modelled. Estimated loss ranges are in estimates/.
GET /api/v1/accounting/<system>.csvCSV
The annual ledger as a table, one row per reach and period (sub-reaches left out).
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/accounting/cvp.csv"
| Column | Type, units | Meaning |
|---|---|---|
system | string | System id. |
reach | string | Reach id. |
reach_name | string | Reach name. |
period | string | WYyyyy or CYyyyy. |
basis | string | As in the JSON. |
head | af | As in the JSON; blank = no record or does not apply. |
inflow | af | |
delivery | af | |
branch | af | |
storage_change | af | |
tail | af | |
residual_total | af | |
operator_loss | af | |
quality | string |
Further columns follow for the terms only some reaches have (inflow_unlisted, delivery_federal, residual_cdec, points_dwr, combined, combined_quality, note).
GET /api/v1/accounting/<system>_monthly.jsonJSON
A system's ledger by month: the same terms per reach and month.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/accounting/friant_monthly.json"
| Field | Type, units | Meaning |
|---|---|---|
system | string | System id. |
unit | string | "af". |
months | YYYY-MM[] | Months of every list below. |
reaches.<reach>.v.<term> | af | null [] | head, inflow, delivery, branch, storage_change, tail and gain (the unmeasured gain (+) or loss (−)), per month. |
reaches.<reach>.q | string[] | Quality per month: measured, estimated, incomplete. |
annual_extra | object | Annual-only figures some reaches have (af). |
GET /api/v1/accounting/<system>_monthly.csvCSV
The monthly ledger as a table, one row per reach and month.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/accounting/swp_monthly.csv"
| Column | Type, units | Meaning |
|---|---|---|
system | string | |
reach | string | |
month | YYYY-MM | |
head | af | Terms as in the JSON; blank = no record or does not apply. |
inflow | af | |
delivery | af | |
branch | af | |
storage_change | af | |
tail | af | |
gain | af | Unmeasured gain (+) or loss (−). |
quality | string |
GET /api/v1/estimates/index.jsonJSON
Estimates, not measurements: loss and open-term ranges, in their own files.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/estimates/index.json"
| Field | Type, units | Meaning |
|---|---|---|
layer | string | "estimate". |
label | string | What an estimate is here. |
files[] | object | name, path, system, about: one file per estimate set; every file says layer: "estimate" at its top. |
Estimates are modelled from published inputs (reference evapotranspiration, rain, canal geometry, dry-year analogues). They are never added into a series, a ledger row or a total, and they are not part of the measured files.
The file layouts are the map's own (as the map loads them) and may change; the top-level layer, system and label fields are frozen.
GET /api/v1/tributaries/index.jsonJSON
Tributary flow requirements: the rivers, compliance points, instruments and requirements the Tributaries Map judges.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/tributaries/index.json"
| Field | Type, units | Meaning |
|---|---|---|
months | YYYY-MM[] | Months the verdict counts cover. |
data_end | YYYY-MM-DD | Last day judged. |
rivers[] | object | id (the <river> of the river paths), name, basin, upper (an upper-watershed system), points (id, name, reach, gauge, reqs), files. |
requirements.<id> | object | title, instrument, section (judged, listed, proposed, reference), judge_label, reason (why it is not judged), gauge, compliance_point, mode (min, band, ramp, rmean, volume, event), effective (from, to), cite, data_notes. |
instruments.<id> | object | label, name, agency, kind. |
gauges.<key> | object | label, series (the source series id), operator. |
excuse | string | What a shortfall means here: the requirement as written; it may have been excused or varied. |
A requirement is judged against the daily mean at its compliance gauge, as written. A shortfall can have been excused or varied (a FERC order, a temporary urgency change); the event markers on the map show the ones on record.
GET /api/v1/tributaries/<river>.csvCSV
One river: per judged requirement and month, the day counts behind the verdict.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/tributaries/mokelumne.csv"
| Column | Type, units | Meaning |
|---|---|---|
river | string | River id. |
compliance_point | string | Point id. |
requirement | string | Requirement id. |
month | YYYY-MM | |
days_in_force | int | Days the requirement applied. |
days_met | int | |
days_below_as_written | int | Days below the minimum as written. |
days_above_band | int | Days above a maximum or band. |
days_nd_year_type_unknown | int | Not determinable: the year type was not published. |
days_nd_no_gauge_value | int | Not determinable: no gauge value. |
days_nd_not_yet_published | int | Not determinable: the data are not yet published. |
days_nd_other_clause | int | Not determinable: a clause of the rule (see the requirement). |
days_nd_temporary_variance | int | Not determinable: a FERC-approved temporary variance. |
days_provisional | int | Days judged on provisional data. |
days_usgs_estimated | int | Days whose gauge value USGS marks estimated. |
days_excepted | int | Days inside an exception period of the rule. |
days_above_written_range_noted | int | Days above the written range, noted. |
lowest | cfs | af | Lowest daily mean (a volume requirement: af to date; an event requirement, mode event: the highest mean over its stated hours in the window, judged once on the window's last day). |
lowest_day | int | Day of the month of the lowest value. |
requirement_low | cfs | af | Lowest required value in the month (the lowest year type it could be). |
requirement_high | cfs | af | Highest required value in the month. |
largest_shortfall_pct | number | Largest shortfall of a day below, % of the minimum. |
Only judged requirements have rows; the rest are listed in index.json with the reason.
api/v1/tributaries/<river>.json has the same counts as lists per requirement (months, and one list per column above).
GET /api/v1/notes.jsonJSON
The data notes: every source defect and pipeline fix found, with evidence, resolution and live status.
curl --compressed "https://storage.googleapis.com/surfacewater-map-509303/api/v1/notes.json"
| Field | Type, units | Meaning |
|---|---|---|
built | YYYY-MM-DD | When the notes were built. |
issues[].id | string | Note id, as in the series' data_notes. |
issues[].title | string | |
issues[].kind | string | source_defect, pipeline_bug, ... |
issues[].source | string | The source it is about. |
issues[].affects | string[] | What it affects. |
issues[].periods | string[] | Periods affected. |
issues[].evidence | string | |
issues[].resolution | string | |
issues[].status | string | |
issues[].live | string | Whether the fix is on the public site. |
issues[].features | string[] | Series ids it is about. |
issues[].reaches | string[] | Ledger reaches it is about. |
issues[].requirements | string[] | Tributary requirement ids it is about. |