8.3 KiB
API Documentation
This document describes the PX-Web API used by hagfish to fetch Faroese fisheries statistics from the official Statbank.
EndpointBase URL: https://statbank.hagstova.fo/api/v1/fo/H2/VV/VV01/fisknv_md.px
- GET: Returns metadata (table structure, dimension codes, labels)
- POST: Returns data in JSON-stat2 format
Endpoint Reference
Health Check
- GET
/healthz- Returns 200 OK if service is running
Species Lookup
- GET
/api/species- Returns list of all species codes and Faroese names
Zones Lookup
- GET
/api/zones- Returns list of all economic zone codes and labels
Gear Lookup
- GET
/api/gear- Returns list of all fishing gear codes and labels
Landings Data
- GET
/api/landings- Returns filtered landing records
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
month |
string | No | Single month filter (legacy, e.g., 2024M01) |
month_from |
string | No | Start of date range (inclusive, e.g., 2024M01) |
month_to |
string | No | End of date range (inclusive, e.g., 2024M12) |
species |
string | No | Filter by species code (e.g., COD) |
gear |
string | No | Filter by gear code (e.g., TR1) |
zone |
string | No | Filter by zone code (e.g., FO) |
measure |
string | No | Filter by measure type (MASS or VALUE) |
limit |
integer | No | Max results (default: 10000, max: 10000) |
Example:
bash curl "http://localhost:8090/api/landings?month_from=2024M01&month_to=2024M06&species=COD&limit=1000"
Summary Aggregates
- GET
/api/summary- Returns aggregated statistics
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
species |
string | No | Filter aggregations by species code |
Response Fields:
monthly: Array of{month, total_mass, total_value}top_species: Array of top 10 species by valueprice_trend: Array of{month, price_per_kg}
Parquet Export
- GET
/api/export.parquet- Downloads full dataset as Parquet file
Note: Triggers temp file creation in system temp directory with automatic cleanup after 300 seconds.
Metadata Request (GET)
Requestbash
curl -s "https://statbank.hagstova.fo/api/v1/fo/H2/VV/VV01/fisknv_md.px"
Response Structurejson
{ "title": "AVR01010 Avreiðingar í nøgd og virði...", "variables": [ { "code": "measure", "text": "mát", "role": null, "values": ["MASS", "VALUE"], "valueTexts": ["Nøgd", "Virði"] } ] }
Fields
| Field | Type | Description |
|---|---|---|
title |
string | Table name in Faroese |
variables[].code |
string | Dimension identifier (used in queries) |
variables[].text |
string | Human-readable label in Faroese |
variables[].role |
null/string | Classification (always null on this endpoint) |
variables[].values |
string[] | Valid dimension codes |
variables[].valueTexts |
string[] | Display labels (parallel with values) |
Verified Dimension Codes
| Variable Code | Faroese Label | Values |
|---|---|---|
measure |
mát | MASS, VALUE |
Species (ASFIS2022) |
Fiskaslag (ASFIS2022) | TOTAL, 148XXXXXXX00000, ... (72 total) |
Fishing Gear (ISSCFG2016) |
Reiðskapur (ISSCFG2016) | TOTAL, ... (12 total) |
Economic Zone (GEONOM2023) |
Búskapar øki (GEONOM2023) | TOTAL, ... (10 total) |
Processing (EUMOFAPresentation) |
Virking (EUMOFAPresentation) | TOTAL, ... (6 total) |
Preservation (EUMOFAPreservation) |
Viðgerð (EUMOFAPreservation) | TOTAL, ... (9 total) |
Shipsize |
Skipastødd | TOTAL, ... (11 total) |
month |
mánaður | 2015M01 through 2026M05 (137 months) |
Note: role is always null. Time dimension identification must use code == "month".
Data Request (POST)
Request Body Formatjson
{ "query": [ { "code": "dimension_code", "selection": { "filter": "item", "values": ["value1", "value2"] } } ], "response": { "format": "json-stat2" } }
Filter Types
| Filter | Values | Description |
|---|---|---|
item |
explicit codes | Select specific values |
all |
["*"] |
Wildcard — select all values |
top |
["TOP_N"] |
Top N values by magnitude |
Example Querybash
curl -s -X POST -H "Content-Type: application/json" -d '{ "query": [ {"code": "month", "selection": {"filter": "item", "values": ["2024M01", "2024M02"]}}, {"code": "Species (ASFIS2022)", "selection": {"filter": "all", "values": [""]}}, {"code": "Fishing Gear (ISSCFG2016)", "selection": {"filter": "all", "values": [""]}}, {"code": "Economic Zone (GEONOM2023)", "selection": {"filter": "all", "values": ["*"]}}, {"code": "Processing (EUMOFAPresentation)", "selection": {"filter": "item", "values": ["TOTAL"]}}, {"code": "Preservation (EUMOFAPreservation)", "selection": {"filter": "item", "values": ["TOTAL"]}}, {"code": "Shipsize", "selection": {"filter": "item", "values": ["TOTAL"]}}, {"code": "measure", "selection": {"filter": "item", "values": ["MASS", "VALUE"]}} ], "response": {"format": "json-stat2"} }' "https://statbank.hagstova.fo/api/v1/fo/H2/VV/VV01/fisknv_md.px"
Cell Limit
- Maximum cells per query: ~8,000,000
- Recommended maximum: 1,000,000 for reliability
- To fetch full dataset, paginate by month or use smaller dimension selections
Response Format (JSON-stat2)
Structurejson
{ "class": "dataset", "label": "...", "id": ["measure", "Species (ASFIS2022)", ..., "month"], "size": [2, 72, 12, 10, 1, 1, 1, 2], "dimension": { "measure": { "label": "mát", "category": { "index": {"MASS": 0, "VALUE": 1}, "label": {"MASS": "Nøgd", "VALUE": "Virði"} } } }, "value": [73653738, 1234567, ...] }
Fields
| Field | Description |
|---|---|
id |
Array of dimension codes in order (defines cube layout) |
size |
Cardinality per dimension (parallel with id) |
dimension.<code>.category.index |
Maps value code → numeric position in dimension |
dimension.<code>.category.label |
Maps value code → display text |
value |
Flattened array of measurements (row-major order) |
Row-Major Index Decoding
Values are stored in row-major order: the last dimension (month) varies fastest.
Given size = [2, 72, 12, 10, 1, 1, 1, 2]:
- Flat index
0→[0,0,0,0,0,0,0,0]= measure=MASS, species=TOTAL, ..., month=2024M01 - Flat index
1→[0,0,0,0,0,0,0,1]= measure=MASS, species=TOTAL, ..., month=2024M02 - Flat index
2→[0,0,0,0,0,0,1,0]= measure=MASS, species=SPECIES_1, ..., month=2024M01
Decoding algorithm (reverse modulo):rust fn decode(flat_index: usize, sizes: &[usize]) -> Vec { let mut indices = Vec::new(); let mut remaining = flat_index; for &size in sizes.iter().rev() { indices.push(remaining % size); remaining /= size; } indices.reverse(); indices }
Sentinel Values
| Value | Meaning |
|---|---|
-1.0 |
Missing/no data (treated as NULL) |
null |
Missing/no data (already nullable in JSON) |
Known Constraints
- Cell limit: ~8 million cells per query
- Rate limits: Unknown — assume reasonable backoff for large downloads
- Language: API responds in requested language (
fooren) - Time zone: No timezone specified — treat timestamps as local
- Updates: Data updated irregularly — metadata timestamp in response header
Integration Checklist
- Metadata endpoint reachable via GET
- POST queries return valid JSON-stat2
- Dimension codes match
variables[].codefrom metadata - Row-major index decoding verified
- Sentinel value coercion (
-1.0→None) implemented - Unicode (Faroese characters) handled correctly
- Incremental ingestion logic tested
- Parquet export tested
- Error handling for network timeouts implemented
Summary of Changes File Change Reason src/api.rs Added MAX_LIMIT constant and hard cap Prevent unlimited query results src/api.rs Replaced CorsLayer::permissive() with explicit origins Security hardening src/api.rs Updated tempfile::Builder with prefix Ensure cleanup function finds files src/api.rs Added test_get_landings_range_filter test Cover new range-filtering logic src/types.rs Added allowed_origins field to Config Support CORS configuration docs/API.md Added endpoint reference table Document new parameters