v1 · Stable

    AQUA-SMART Public API

    A read-only REST API for monitoring stations, time-series observations, computed indicators, organizations and projects across the Black Sea aquaculture network.

    1. Quick start

    1. Request a key. Fill in the short access-request form below — tell us who you are and what you'll build. We review every request and reply by email, usually within 2 business days, with your personal API key.
    2. Copy the key immediately when you receive it — it is shown only once.
    3. Send the key in the 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/stations

    2. Base URL

    https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api

    All endpoints are versioned under /v1/. The OpenAPI 3.0 spec is available at /v1/openapi.json.

    3. Authentication

    Every request must include a valid API key, sent as either:

    • Header: X-API-Key: <your_key> (recommended)
    • Query string: ?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.

    4. Endpoints

    Public data (X-API-Key)

    GET
    /v1/openapi.jsonOpenAPI 3.0 spec
    GET
    /v1/stationsList all monitoring stations
    GET
    /v1/stations/:idGet one station by id
    GET
    /v1/stations/snapshotLatest snapshot for every station (WQI, risk, eddy, health)
    GET
    /v1/stations/:id/observationsTime-series readings (SST, salinity, Chl-a, …)
    GET
    /v1/stations/:id/indicatorsDaily indicators (WQI, DO mg/L, sat%, SSH, health 1–5)
    GET
    /v1/stations/:id/feeding-guideRation % + rules from DO/pH/SST envelopes
    GET
    /v1/stations/:id/diver-safetySafe / caution / unsafe rating + jellyfish risk
    GET
    /v1/stations/:id/bloom-eventsChl-a bloom events with eddy lag + WQI drop
    GET
    /v1/stations/daily-storyToday's AI-written ocean narrative (cached)
    GET
    /v1/organizationsConsortium organizations
    GET
    /v1/projectsPublic projects
    GET
    /v1/blogList blog posts (title, excerpt, tags, views, url)
    GET
    /v1/blog/:slugGet one blog post by slug

    Mobile-app user routes (Authorization: Bearer JWT)

    POST
    /v1/push-tokensRegister a mobile push token
    DELETE
    /v1/push-tokensUnregister a push token
    GET
    /v1/push-tokens/preferencesGet notification preferences
    PUT
    /v1/push-tokens/preferencesUpdate notification preferences
    POST
    /v1/citizen-scienceSubmit a field observation (+ optional photo)
    GET
    /v1/citizen-science/myList my last 10 observations

    These 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.

    5. Endpoint details

    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.

    GET

    /v1/stations

    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: $KEY

    Response · 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

    /v1/stations/:id

    X-API-Key

    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: $KEY

    Response · 200 OK

    {
      "data": {
        "id": "ro",
        "country": "Romania",
        "name": "Danube Delta",
        "location": "Danube Delta",
        "lat": 44.86,
        "lng": 29.5
      }
    }
    GET

    /v1/stations/snapshot

    X-API-Key

    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: $KEY

    Response · 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"
    }
    GET

    /v1/stations/:id/observations

    X-API-Key

    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: $KEY

    Response · 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 }
      ]
    }
    GET

    /v1/stations/:id/indicators

    X-API-Key

    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: $KEY

    Response · 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
        }
      ]
    }
    GET

    /v1/stations/:id/feeding-guide

    X-API-Key

    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: $KEY

    Response · 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"
    }
    GET

    /v1/stations/:id/diver-safety

    X-API-Key

    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: $KEY

    Response · 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"
    }
    GET

    /v1/stations/:id/bloom-events

    X-API-Key

    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: $KEY

    Response · 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
        }
      ]
    }
    GET

    /v1/stations/daily-story

    X-API-Key

    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: $KEY

    Response · 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"
      }
    }
    GET

    /v1/organizations

    X-API-Key

    Consortium organizations (public profile + country + expertise).

    Request

    GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/organizations
    X-API-Key: $KEY

    Response · 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
        }
      ]
    }
    GET

    /v1/projects

    X-API-Key

    Public projects, sorted by start_date DESC.

    Request

    GET https://nohfpjovcatvjbzebwus.supabase.co/functions/v1/public-api/v1/projects
    X-API-Key: $KEY

    Response · 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
        }
      ]
    }
    POST

    /v1/push-tokens

    Bearer JWT

    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 } }
    DELETE

    /v1/push-tokens

    Bearer JWT

    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 } }
    GET

    /v1/push-tokens/preferences

    Bearer JWT

    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_JWT

    Response · 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"
      }
    }
    PUT

    /v1/push-tokens/preferences

    Bearer JWT

    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 } }
    POST

    /v1/citizen-science

    Bearer JWT

    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"
      }
    }
    GET

    /v1/citizen-science/my

    Bearer JWT

    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_JWT

    Response · 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
    }
    GET

    /v1/blog

    X-API-Key

    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: $KEY

    Response · 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"
        }
      ]
    }
    GET

    /v1/blog/:slug

    X-API-Key

    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: $KEY

    Response · 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"
      }
    }

    6. Code samples

    JavaScript (fetch)

    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);

    Python (requests)

    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])

    R (httr2)

    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)$data

    7. Errors

    Errors return a JSON body with HTTP status code:

    { "error": { "code": "unauthorized", "message": "Missing or invalid API key" } }
    StatusCodeMeaning
    400bad_requestInvalid query parameter
    401unauthorizedMissing / wrong API key
    403forbiddenKey revoked or out of scope
    404not_foundUnknown station / resource
    429rate_limitedToo many requests — back off

    8. Rate limits & fair use

    • Default soft limit: 600 requests / hour / key.
    • Maximum limit per request: 1000 rows.
    • Use from/to to page through long time series.
    • Cache responses where possible — observations refresh every 5 min, indicators daily.

    9. Data attribution & licence

    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.

    10. Support

    Questions, bug reports or higher rate-limit requests: info@eaquaf.eu · +30 694 935 6244