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-NC 4.0. Additional permission: public agencies, groundwater sustainability agencies, consultants working for them, and academic researchers may also use these outputs for commercial purposes, with the same credit. Other commercial use needs the author's written permission. Releases up to 2026-10-29T0510 remain under CC BY 4.0. Cite the release you used:
Gray, W. (2026). Central Valley Surfacewater Map, release <release>. CC BY-NC 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-NC.
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-NC 4.0". |
licence_url | string | URL of the licence. |
licence_note | string | The additional permission for public agencies, GSAs, their consultants and academic researchers, and the earlier releases' 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-NC 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 flows to 1 cfs, monthly means to 0.01 cfs and water-year means to 0.001 cfs, so a month's or a year's mean times its days and 1.98347 gives its acre-feet to the whole acre-foot. Storage is acre-feet: daily to 1, monthly and water-year to 0.1.
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. |