A read-only REST API for monitoring stations, time-series observations, computed indicators, organizations and projects across the Black Sea aquaculture network.
X-API-Key header on every request.Project administrators can also generate keys directly from Profile → API Keys.
curl -H "X-API-Key: YOUR_KEY" \
https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stationshttps://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-apiAll endpoints are versioned under /v1/. The OpenAPI 3.0 spec is available at /v1/openapi.json.
Every request must include a valid API key, sent as either:
X-API-Key: <your_key> (recommended)?api_key=<your_key> (for quick tests)Keys are stored hashed (SHA-256) and cannot be recovered if lost — if you lose a key, reply to your approval email or submit a new access request and we'll issue a replacement. Each request updates the key’s last_used_at timestamp for audit.
Keep keys secret. Never commit them to git or expose them in browser code. Use them only from your server, scheduled job, or trusted notebook.
/v1/openapi.jsonOpenAPI 3.0 spec/v1/stationsList all monitoring stations/v1/stations/:idGet one station by id/v1/stations/snapshotLatest snapshot for every station (WQI, risk, eddy, health)/v1/stations/:id/observationsTime-series readings (SST, salinity, Chl-a, …)/v1/stations/:id/indicatorsDaily indicators (WQI, DO mg/L, sat%, SSH, health 1–5)/v1/stations/:id/feeding-guideRation % + rules from DO/pH/SST envelopes/v1/stations/:id/diver-safetySafe / caution / unsafe rating + jellyfish risk/v1/stations/:id/bloom-eventsChl-a bloom events with eddy lag + WQI drop/v1/stations/daily-storyToday's AI-written ocean narrative (cached)/v1/organizationsConsortium organizations/v1/projectsPublic projects/v1/blogList blog posts (title, excerpt, tags, views, url)/v1/blog/:slugGet one blog post by slug/v1/push-tokensRegister a mobile push token/v1/push-tokensUnregister a push token/v1/push-tokens/preferencesGet notification preferences/v1/push-tokens/preferencesUpdate notification preferences/v1/citizen-scienceSubmit a field observation (+ optional photo)/v1/citizen-science/myList my last 10 observationsThese routes are scoped to the signed-in user via their Cloud JWT — pass it as Authorization: Bearer <access_token>. TheX-API-Key header is not used here.
Every endpoint below shows a full HTTP request and a sample JSON response body. Authenticated mobile-app routes use Authorization: Bearer instead of X-API-Key.
List every monitoring station with static metadata.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations
X-API-Key: $KEYResponse · 200 OK
{
"data": [
{
"id": "ro",
"country": "Romania",
"name": "Danube Delta",
"location": "Danube Delta",
"lat": 44.86,
"lng": 29.5
},
{
"id": "bg",
"country": "Bulgaria",
"name": "Varna",
"location": "Varna",
"lat": 43.2,
"lng": 28.05
},
{
"id": "ge",
"country": "Georgia",
"name": "Batumi",
"location": "Batumi",
"lat": 41.7,
"lng": 41.7
}
],
"count": 3
}Get one station by id. Valid ids: ro, bg, ge.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/ro
X-API-Key: $KEYResponse · 200 OK
{
"data": {
"id": "ro",
"country": "Romania",
"name": "Danube Delta",
"location": "Danube Delta",
"lat": 44.86,
"lng": 29.5
}
}Latest snapshot (WQI, mortality risk, eddy, bloom watch, health) for every station — ideal for a mobile home screen.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/snapshot
X-API-Key: $KEYResponse · 200 OK
{
"data": [
{
"station_id": "ro",
"name": "Danube Delta",
"country": "Romania",
"wqi": 72,
"mortality_risk": "low",
"bloom_risk_pct": 25,
"do_mgl": 8.4,
"do_saturation_pct": 96.1,
"ph": 8.12,
"sst_c": 18.4,
"ssh_cm": 8.2,
"eddy_active": false,
"bloom_watch_days": null,
"health_score": 4,
"updated_at": "2026-06-01T05:30:00Z"
},
{
"station_id": "bg",
"name": "Varna",
"country": "Bulgaria",
"wqi": 68,
"mortality_risk": "low",
"bloom_risk_pct": 45,
"do_mgl": 7.9,
"do_saturation_pct": 92.4,
"ph": 8.08,
"sst_c": 19.1,
"ssh_cm": 12.3,
"eddy_active": false,
"bloom_watch_days": 2,
"health_score": 4,
"updated_at": "2026-06-01T05:30:00Z"
}
],
"count": 3,
"generated_at": "2026-06-01T05:35:00Z"
}Time-series readings. Query: from, to (ISO 8601), parameter, limit (default 500, max 1000). Sorted observed_at DESC.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/ro/observations?parameter=sst&from=2026-05-01&limit=3
X-API-Key: $KEYResponse · 200 OK
{
"station_id": "ro",
"count": 3,
"data": [
{ "observed_at": "2026-05-01T02:00:00Z", "parameter": "sst", "value": 15.74, "unit": "degC", "source": "CMEMS", "depth_m": 0 },
{ "observed_at": "2026-05-01T01:00:00Z", "parameter": "sst", "value": 15.79, "unit": "degC", "source": "CMEMS", "depth_m": 0 },
{ "observed_at": "2026-05-01T00:00:00Z", "parameter": "sst", "value": 15.82, "unit": "degC", "source": "CMEMS", "depth_m": 0 }
]
}Daily indicators enriched with DO mg/L + saturation% (García & Gordon 1992), SSH, eddy_active and a 1–5 health score.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/ro/indicators?from=2026-05-30&to=2026-06-01
X-API-Key: $KEYResponse · 200 OK
{
"station_id": "ro",
"count": 2,
"data": [
{
"observed_on": "2026-05-31",
"wqi": 73,
"thermal_anomaly": 0.2,
"bloom_risk": 24,
"details": { "chla_mean_ugl": 1.6, "sst_mean_c": 18.3 },
"computed_at": "2026-06-01T03:00:00Z",
"do_mgl": 8.5,
"do_saturation_pct": 97.0,
"eddy_active": false,
"ssh_cm": 8.0,
"health_score": 4
},
{
"observed_on": "2026-05-30",
"wqi": 71,
"thermal_anomaly": 0.4,
"bloom_risk": 22,
"details": { "chla_mean_ugl": 1.4, "sst_mean_c": 18.5 },
"computed_at": "2026-05-31T03:00:00Z",
"do_mgl": 8.3,
"do_saturation_pct": 95.4,
"eddy_active": false,
"ssh_cm": 7.1,
"health_score": 4
}
]
}Feed ration % + timing. DO ≥115% sat or pH ≥8.6 → level=stop; out-of-band SST or low DO → level=reduced.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/ro/feeding-guide
X-API-Key: $KEYResponse · 200 OK
{
"station_id": "ro",
"ration_pct": 70,
"timing": "Single midday feed only",
"rules": [
"SST 19.1°C is in the marginal zone — reduce ration ~30%."
],
"level": "reduced",
"inputs": {
"sst_c": 19.1,
"do_mgl": 8.4,
"ph": 8.12,
"salinity_psu": 17.8,
"do_saturation_pct": 96.1
},
"computed_at": "2026-06-01T05:35:00Z"
}Diver safety derived from wave Hs, current speed, Kd490 visibility, SST and chl-a/jellyfish risk.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/bg/diver-safety
X-API-Key: $KEYResponse · 200 OK
{
"station_id": "bg",
"rating": "caution",
"colour": "amber",
"wave_hs_m": 1.4,
"current_ms": 0.32,
"visibility": "moderate",
"water_temp_c": 17.2,
"jellyfish_risk": "low",
"best_window": "Midday window, light surface chop",
"notes": [
"Wave Hs 1.40 m — near 1.0 m limit, caution.",
"Current 0.32 m/s — at safe limit.",
"Kd490 0.62 — moderate visibility."
],
"computed_at": "2026-06-01T05:35:00Z"
}Chl-a runs ≥3 µg/L for ≥3 consecutive days, with the lagged SSH eddy that preceded them. Query: year (default current year), severity (any|moderate|severe).
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/bg/bloom-events?year=2026&severity=severe
X-API-Key: $KEYResponse · 200 OK
{
"station_id": "bg",
"year": 2026,
"severity": "severe",
"count": 1,
"data": [
{
"start_date": "2026-04-18",
"end_date": "2026-04-25",
"duration_days": 8,
"peak_chla_ugl": 11.4,
"mean_chla_ugl": 6.8,
"severity": "severe",
"eddy_preceded": true,
"eddy_lag_days": 14,
"wqi_drop": 23
}
]
}Today's AI-written Black Sea narrative, cached once per day in daily_stories.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/daily-story
X-API-Key: $KEYResponse · 200 OK
{
"data": {
"story": "Sea-surface temperatures rose 0.4 °C overnight while chlorophyll-a held below 3 µg/L at every station. The Danube Delta plume is narrowing; Varna shows the first clean week of June…",
"wqi_snapshot": { "ro": 72, "bg": 68, "ge": 74 },
"generated_at": "2026-06-01T04:00:00Z"
}
}Consortium organizations (public profile + country + expertise).
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/organizations
X-API-Key: $KEYResponse · 200 OK
{
"count": 9,
"data": [
{
"id": "8a1c…",
"name": "Reinvent Education",
"acronym": "RE",
"org_type": "ngo",
"country": "Greece",
"city": "Athens",
"website": "https://reinventeducation.gr",
"contact_email": "info@reinventeducation.gr",
"expertise_tags": ["education", "aquaculture"],
"verified": true
}
]
}Public projects, sorted by start_date DESC.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/projects
X-API-Key: $KEYResponse · 200 OK
{
"count": 14,
"data": [
{
"id": "e-aquaf",
"title": "e-AQUAF — Black Sea Aquaculture Observatory",
"acronym": "e-AQUAF",
"programme": "Interreg NEXT BSB",
"call_identifier": "BSB-2024-01",
"start_date": "2024-09-01",
"end_date": "2027-08-31",
"region": "Black Sea",
"eu_funding_amount": 2400000
}
]
}Register a mobile FCM/APNs push token for the signed-in user (upsert on user_id + fcm_token).
Request
POST https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/push-tokens
Authorization: Bearer $USER_JWT
Content-Type: application/json
{
"fcm_token": "fEs9-XYZ…",
"platform": "android",
"preferences": {
"mortality_elevated": true,
"wqi_drop_6pts": true,
"bloom_watch": true,
"do_supersaturation": true,
"wqi_below_50": false
}
}Response · 200 OK
{ "data": { "registered": true } }Unregister a push token.
Request
DELETE https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/push-tokens
Authorization: Bearer $USER_JWT
Content-Type: application/json
{ "fcm_token": "fEs9-XYZ…" }Response · 200 OK
{ "data": { "removed": true } }Read the signed-in user's notification preferences (defaults applied when never set).
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/push-tokens/preferences
Authorization: Bearer $USER_JWTResponse · 200 OK
{
"data": {
"preferences": {
"mortality_elevated": true,
"wqi_drop_6pts": true,
"bloom_watch": true,
"do_supersaturation": true,
"wqi_below_50": false
},
"updated_at": "2026-05-30T11:22:00Z"
}
}Replace notification preferences. Accepts either { preferences: {...} } or the bare object.
Request
PUT https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/push-tokens/preferences
Authorization: Bearer $USER_JWT
Content-Type: application/json
{
"preferences": {
"mortality_elevated": true,
"wqi_drop_6pts": true,
"bloom_watch": true,
"do_supersaturation": true,
"wqi_below_50": true
}
}Response · 200 OK
{ "data": { "updated": true } }Submit a field observation with an optional base64 photo (JPEG/PNG, ≤8 MB) uploaded to the citizen-observations bucket.
Request
POST https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/citizen-science
Authorization: Bearer $USER_JWT
Content-Type: application/json
{
"station_id": "ge",
"lat": 41.7,
"lng": 41.7,
"water_colour": "brown-green",
"foam": true,
"fish_behaviour": "surface gulping",
"visibility_cm": 35,
"notes": "Strong onshore wind, smell of decaying algae.",
"photo_base64": "data:image/jpeg;base64,/9j/4AAQSk…"
}Response · 200 OK
{
"data": {
"id": "0c8a1f3e-1a2b-4c5d-9e0f-112233445566",
"submitted_at": "2026-06-01T05:35:00Z",
"photo_url": "https://nohfpjovcatvjbzebwus.supabase.co/storage/v1/object/public/citizen-observations/<user_id>/<uuid>.jpg"
}
}List the signed-in user's last 10 submissions (most recent first).
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/citizen-science/my
Authorization: Bearer $USER_JWTResponse · 200 OK
{
"data": [
{
"id": "0c8a1f3e-1a2b-4c5d-9e0f-112233445566",
"station_id": "ge",
"lat": 41.7,
"lng": 41.7,
"water_colour": "brown-green",
"foam": true,
"fish_behaviour": "surface gulping",
"visibility_cm": 35,
"notes": "Strong onshore wind, smell of decaying algae.",
"photo_url": "https://nohfpjovcatvjbzebwus.supabase.co/storage/v1/object/public/citizen-observations/<user_id>/<uuid>.jpg",
"submitted_at": "2026-06-01T05:35:00Z"
}
],
"count": 1
}List published AQUA-SMART blog posts with live view counts and canonical URLs.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/blog
X-API-Key: $KEYResponse · 200 OK
{
"count": 3,
"data": [
{
"slug": "introducing-aquasmart-public-api",
"title": "Introducing the AQUA-SMART Public API — Black Sea Data for Your App",
"excerpt": "A complete developer guide to the AQUA-SMART Public REST API...",
"category": "Developers",
"read_time": "10 min read",
"published_at": "2026-06-01",
"tags": ["api", "developers", "rest", "mobile"],
"views": 142,
"url": "https://eaquaf.eu/blog/introducing-aquasmart-public-api"
}
]
}Fetch a single blog post by slug, including its current view count.
Request
GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/blog/ask-the-sea-ai-assistant
X-API-Key: $KEYResponse · 200 OK
{
"data": {
"slug": "ask-the-sea-ai-assistant",
"title": "Meet Ask the Sea — A Natural-Language AI Assistant for the Black Sea Observatory",
"excerpt": "How we built a tool-augmented Gemini 3 Flash assistant...",
"category": "Product",
"read_time": "9 min read",
"published_at": "2026-05-20",
"tags": ["ai", "assistant", "gemini", "product"],
"views": 87,
"url": "https://eaquaf.eu/blog/ask-the-sea-ai-assistant"
}
}const res = await fetch(
"https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations",
{ headers: { "X-API-Key": process.env.AQUASMART_KEY } }
);
const { data } = await res.json();
console.log(data);import os, requests
r = requests.get(
"https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations/bg-varna-east/observations",
params={"parameter": "sst", "limit": 500},
headers={"X-API-Key": os.environ["AQUASMART_KEY"]},
timeout=30,
)
r.raise_for_status()
print(r.json()["data"][:3])library(httr2)
resp <- request("https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/stations") |>
req_headers(`X-API-Key` = Sys.getenv("AQUASMART_KEY")) |>
req_perform()
resp_body_json(resp)$dataErrors return a JSON body with HTTP status code:
{ "error": { "code": "unauthorized", "message": "Missing or invalid API key" } }| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Invalid query parameter |
| 401 | unauthorized | Missing / wrong API key |
| 403 | forbidden | Key revoked or out of scope |
| 404 | not_found | Unknown station / resource |
| 429 | rate_limited | Too many requests — back off |
limit per request: 1000 rows.from/to to page through long time series.Data is provided under CC BY 4.0. When publishing, please attribute:
Source: AQUA-SMART platform (eaquaf.eu), incorporating Copernicus Marine,
NASA GIBS, PACE, and FAO FishStatJ data.Questions, bug reports or higher rate-limit requests: info@eaquaf.eu · +30 694 935 6244