Everything you need to understand and use FlowFabric in 10 minutes.
FlowFabric is a high-performance REST API for accessing National Water Model (NWM) forecasts, reanalysis data, and hydrologic analysis datasets. Built for hydrologic modeling, flood mapping, water resource planning, and real-time operational systems.
Get streamflow predictions and historical flows for any catchment in <8 seconds. No data downloads, no processing delays—just efficient streaming data queries.
Production-grade infrastructure with authentication, metering, cost estimation, and sub-second query performance. Designed for internal tools, dashboards, and enterprise applications. 2-3x faster than competitors, 50-70% cheaper per query.
Scenario: A water utility needs to update forecast streamflow on a dashboard every 30 minutes.
FlowFabric Solution:
# Update 50 critical gages in <500ms
import httpx
gages = [101, 1001, 10001, 100001, 1000001] # 5 critical locations
response = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm-forecast/streamflow",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"issue_time": "latest",
"scope": "features",
"feature_ids": gages
}
).json()
# Render on dashboard immediately (not waiting for downloads)
Cost: ~$0.0001 per query (50 locations) Latency: 150-400ms total (API + network) Alternative (NOAA API): 30-45 seconds, free but too slow for dashboards
Scenario: Research team running 10,000 model simulations needs historical streamflow for calibration.
FlowFabric Solution:
# Export 10 years of data for 100 features to S3 (processed offline)
estimate = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm-reanalysis/streamflow?estimate=true",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"start_time": "2013-01-01",
"end_time": "2023-12-31",
"scope": "features",
"feature_ids": ["8318793", "8318787", "8318775", "8318785", "8318789", "8318801", "8318819", "8318813", "8318825", "8318841", "8318835", "8318829", "8318827", "8318843", "8318847", "8318851", "8318845", "8318849", "8318861", "8318859", "8318853", "8318855", "8318857", "8318867", "8318863", "8318865", "8318869", "8318871", "8318873", "8318879", "8318877", "8318881", "8318883", "8318885", "8318887", "8318889", "8318891", "8318893", "8318895", "8318897", "8318899", "8318901", "8318903", "8318905", "8318907", "8318909", "8318911", "8318913", "8318915", "8318917"]
}
).json()
print(f"Cost: ${estimate['estimated_cost']} | Size: {estimate['estimated_gb']}GB")
# Export (background job)
export = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm-reanalysis/streamflow",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"start_time": "2013-01-01",
"end_time": "2023-12-31",
"scope": "features",
"feature_ids": ["8318793", "8318787", "8318775", "8318785", "8318789", "8318801", "8318819", "8318813", "8318825", "8318841", "8318835", "8318829", "8318827", "8318843", "8318847", "8318851", "8318845", "8318849", "8318861", "8318859", "8318853", "8318855", "8318857", "8318867", "8318863", "8318865", "8318869", "8318871", "8318873", "8318879", "8318877", "8318881", "8318883", "8318885", "8318887", "8318889", "8318891", "8318893", "8318895", "8318897", "8318899", "8318901", "8318903", "8318905", "8318907", "8318909", "8318911", "8318913", "8318915", "8318917"],
"mode": "export"
}
).json()
print(f"Download at: {export['download_url']}") # S3 link ready in 30-60s
Cost: ~$0.50-1.00 (100 features × 10 years) Time: 30-60 seconds to generate + background download Alternative (Manual BigQuery): ~$5-10 in compute costs, requires SQL knowledge, 2-3 hours setup
Scenario: NWS office needs current water surface elevation for 500 stream gages.
FlowFabric Solution:
# Get latest stage and translate to elevation
response = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm-analysis/stage:query",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"issue_time": "latest",
"scope": "features",
"feature_ids": ["8318793", "8318787", "8318775", "8318785", "8318789", "8318801", "8318819", "8318813", "8318825", "8318841"] # 10 valid NHM reaches
}
)
df = pd.read_parquet(io.BytesIO(response.content))
print(df.head())
# feature_id timestamp stage
# 0 1 2026-01-09 0.75
# 1 10 2026-01-09 1.21
# ...
Performance: 250-600ms for 500 gages Cost: ~$0.001 per query Alternative (NOAA API): 45-90 seconds per gage (would need 450+ API calls), rate-limited
All available datasets are listed dynamically in the API. Browse the full catalog:
curl https://flowfabric-api.lynker-spatial.com/v1/datasets
Available Datasets:
Each supports streamflow queries, historical analysis, and export modes.
All responses use Arrow IPC (columnar binary format): - 50-70% smaller than JSON for same data (binary encoding) - Zero-copy deserialization - instant load in Python, R, JavaScript - Perfect for big data - works seamlessly with pandas, polars, DuckDB - Streaming support - read data while still downloading
Performance comparison (1,000 streamflow records): - Arrow IPC: 45KB, 1ms to deserialize - JSON: 120KB, 50ms to deserialize - CSV: 95KB, 150ms to parse
Sign in via SSO at https://flowfabric-api.lynker-spatial.com/oauth/login — your session is set automatically. For programmatic access, self-issue an API key at POST /v1/me/keys. All requests require:
Authorization: Bearer <your-token>
Go to https://flowfabric-api.lynker-spatial.com/oauth/login and authenticate with your Lynker Spatial account. You'll be redirected back to the docs with your session active.
For scripts or CI, self-issue an API key: POST /v1/me/keys (requires an active SSO session).
Open https://flowfabric-api.lynker-spatial.com/docs — if you signed in via SSO you're already authenticated. To use a Bearer token directly, click Authorize and paste it.
Python:
import httpx
import pyarrow.ipc as ipc
import io
TOKEN = "your-token-here"
# Estimate query cost
estimate = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow?estimate=true",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"issue_time": "2026010618",
"scope": "features",
"feature_ids": ["101", "1001"]
}
).json()
print(f"Query will return {estimate['estimated_rows']} rows, {estimate['estimated_bytes']} bytes")
# Execute query
response = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"issue_time": "2026010618",
"scope": "features",
"feature_ids": ["101", "1001"],
"mode": "sync"
}
)
# Parse Arrow response
reader = ipc.open_stream(io.BytesIO(response.content))
df = reader.read_pandas()
print(df.head())
R:
library(httr)
library(arrow)
TOKEN <- "your-token-here"
# Estimate query cost
estimate <- POST(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow?estimate=true",
add_headers(Authorization = paste("Bearer", TOKEN)),
body = list(
issue_time = "2026010618",
scope = "features",
feature_ids = c("8318793", "8318787")
),
encode = "json"
)
estimate_json <- content(estimate, as = "parsed")
cat(sprintf("Query will return %d rows, %d bytes\n",
estimate_json$estimated_rows,
estimate_json$estimated_bytes))
# Execute query
response <- POST(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow",
add_headers(Authorization = paste("Bearer", TOKEN)),
body = list(
issue_time = "2026010618",
scope = "features",
feature_ids = c("8318793", "8318787"),
mode = "sync"
),
encode = "json"
)
# Parse Arrow response
df <- read_ipc(response$content)
head(df)
Python:
# Get latest forecast for USGS gauge 01022500
response = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"issue_time": "latest",
"scope": "features",
"feature_ids": ["8318793"], # NHM feature ID
"mode": "sync"
}
)
R:
response <- POST(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/forecast/streamflow",
add_headers(Authorization = paste("Bearer", TOKEN)),
body = list(
issue_time = "latest",
scope = "features",
feature_ids = c("8318793"), # NHM feature ID
mode = "sync"
),
encode = "json"
)
df <- read_ipc(response$content)
Python:
response = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/reanalysis/streamflow",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"start_time": "2010-01-01",
"end_time": "2020-12-31",
"scope": "catchments",
"region": "conus",
"mode": "export" # Returns S3 download link instead of streaming
}
)
export_result = response.json()
print(f"Download at: {export_result['download_url']}")
R:
response <- POST(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/reanalysis/streamflow",
add_headers(Authorization = paste("Bearer", TOKEN)),
body = list(
start_time = "2010-01-01",
end_time = "2020-12-31",
scope = "catchments",
region = "conus",
mode = "export"
),
encode = "json"
)
export_result <- content(response, as = "parsed")
cat(sprintf("Download at: %s\n", export_result$download_url))
Python:
estimate = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/reanalysis/streamflow?estimate=true",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"start_time": "2000-01-01",
"end_time": "2023-12-31",
"scope": "region",
"region": "conus"
}
).json()
print(f"Cost: ${estimate['estimated_cost']}")
print(f"Recommended mode: {estimate['recommended_mode']}") # sync or export
R:
estimate <- POST(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/nwm/reanalysis/streamflow?estimate=true",
add_headers(Authorization = paste("Bearer", TOKEN)),
body = list(
start_time = "2000-01-01",
end_time = "2023-12-31",
scope = "region",
region = "conus"
),
encode = "json"
)
estimate_json <- content(estimate, as = "parsed")
cat(sprintf("Cost: $%s\n", estimate_json$estimated_cost))
cat(sprintf("Recommended mode: %s\n", estimate_json$recommended_mode))
| Problem | Solution |
|---|---|
| 401 Unauthorized | Token expired or invalid. Sign in again at https://flowfabric-api.lynker-spatial.com/oauth/login or re-issue an API key at POST /v1/me/keys |
| 400 Bad Request | Check dataset_id, feature_ids, and date formats. Use /docs to validate payloads. |
| 503 Service Unavailable | Catalog service down. Check https://status.lynker-spatial.com |
| Query exceeds limits | Use mode: export or split into smaller requests. See rate limits below. |
Query size limits and pricing vary by dataset and plan. Before executing large queries:
?estimate=true query parameter to see query size and cost# Check estimated cost and recommended mode
estimate = httpx.post(
"https://flowfabric-api.lynker-spatial.com/v1/datasets/{dataset}/streamflow?estimate=true",
headers={"Authorization": f"Bearer {TOKEN}"},
json={...} # your query parameters
).json()
print(f"Estimated cost: {estimate.get('cost')}")
print(f"Recommended mode: {estimate.get('recommended_mode')}")
git clone https://github.com/lynker-spatial/flowfabric-apipip install -r requirements.txtcp .env.example .envuvicorn app.main:app --reloadThe repo README covers how to add new datasets, create adapters, add endpoints, and run the test suite.