GeoVeil API Documentation

GeoVeil exposes two API versions for access to the GNSS interference data of the ROMPOS system (86 active stations, 83 with spectrum analysis), operated by ANCPI through the National Centre for Cartography (CNC). V2 is the primary API — live data, spoofing analysis and ML prediction. V1 is the classic API with historical perspective and VADASE data.

Overview

The ROMPOS system has 86 active CORS stations, operated by ANCPI through CNC. GeoVeil connects to 83 stations with spectrum-analysis capability (NovAtel OEM7), streaming ITDETECTSTATUSA and SPECTRUMANALYSIS in real time, processed for interference, spoofing and meaconing detection. 74 stations hold a VADASE licence for positional displacement analysis.

Authentication

The APIs are public and require no authentication for read operations. Administrative endpoints are not publicly documented and require authentication.

Base URLs

V2 — Live (primary)
https://api.geoveil.ro/v2
V1 — Classic
https://api.geoveil.ro/v2/v1

Formats

All responses are application/json. Timestamps are ISO 8601 UTC. Frequencies in MHz, bandwidths in MHz, power loss in dBm.

GET /i — Interference events (V2)

V2
GET /i Events by station + date range

Returns interference events for a station within a date range.

Parameters
ParameterTypeDescription
station_codestringreqStation code (e.g. SULI)
start_datedatereqStart date (YYYY-MM-DD)
end_datedatereqEnd date (YYYY-MM-DD)
Response
[
  {
    "id": 12345,
    "stationid": "SULI",
    "datetime": "2026-05-26T14:23:00Z",
    "rfpath": "L1",
    "centfreq_mhz": 1575.42,
    "bandwidth_mhz": 10.0,
    "powerloss_dbm": -42.5,
    "hestpowspecdens_dbmhz": -95.3
  },
  ...
]

GET /lh — Last hour (V2)

V2
GET /lh All events in the last 60 minutes

Returns the raw events from the last hour. Without parameters it returns all stations (potentially large — 100k+ events). With station_code it filters per station; limit caps the row count (recommended when opening a station modal). For aggregate views, prefer /lh/summary.

Optional parameters
ParameterTypeDescription
station_codestringoptFilter per station (e.g. SULI)
limitintoptMaximum rows returned (ORDER BY datetime DESC). Recommended: 3000 when station_code is set.
Response
[
  {
    "stationid": "SULI",
    "datetime": "2026-05-26T12:14:00Z",
    "rfpath": "L1",
    "centfreq_mhz": 1575.42,
    "bandwidth_mhz": 8.0,
    "powerloss_dbm": -38.2,
    "hestpowspecdens_dbmhz": -92.1
  },
  ...
]

GET /lh/summary — Last-hour summary (V2)

V2
GET /lh/summary Per-station aggregated summary of the last hour

Returns one row per active station with pre-aggregated stats: event count, dominant band, minimum power loss, 6 ten-minute time buckets and the best spoofing analysis. Ideal for the Threats page — returns ~32 rows instead of 100k+ raw events.

Parameters

None. Returns all stations active during the last hour.

Response
[
  {
    "station_code": "SULI",
    "stationid": 64,
    "count": 4823,
    "dom_rfpath": "L1",
    "min_powerloss": -61.4,
    "buckets": [820, 910, 750, 890, 780, 673],
    "threat_type": "spoofing_candidate",
    "confidence": 0.87,
    "severity": "HIGH"
  },
  ...
]
Response fields
FieldDescription
station_codeStation code (e.g. SULI)
stationidInternal numeric ID
countTotal events in the last hour
dom_rfpathDominant RF band (e.g. L1)
min_powerlossLargest power loss (dBm, most negative)
bucketsArray of 6 values: event count per 10-minute window (chronological)
threat_typeSpoofing threat type (null if no analysis today)
confidenceSpoofing analysis confidence score (0–1)
severityHIGH / MEDIUM / LOW / null

GET /now — Right-now station snapshot (V2)

V2
GET /now Last-N-min snapshot per station: classified events + sweep detection + V1 enrichment

Returns the last-N-min interference snapshot for one station, with per-event PSD threat level + bandwidth shape, summary aggregates, sweep/chirp pattern detection over a wider context, and best-effort V1 enrichment of overlapping events.

Parameters
ParameterTypeDescription
station_codestringreqV2 station code (e.g. COST)
windowintoptSnapshot window in seconds, 1–900 (default 120)
contextintoptWider lookback in seconds for sweep detection (window..3600, default 600)
include_v10/1optEnrich with V1 events that overlap the window (default 1)
Threat levels (PSD, dBm/Hz)
LevelPSD
noise_floor≤ -130
info-130 .. -120
noticeable-120 .. -110
significant-110 .. -103
high-103 .. -98
critical> -98
Shape (by bandwidth_mhz)

narrowband ≤ 5 MHz · chirp 5..20 MHz · wideband > 20 MHz

Response (abridged)
{
  "station": {"station_code": "COST", "station_name": "Constanta", "lat": 44.16, "lon": 28.66},
  "window":  {"from": "...", "to": "...", "seconds": 120, "context_seconds": 600},
  "summary": {
    "total_events": 47,
    "max_threat_level": "high",
    "by_threat_level": {"noise_floor": 5, "info": 10, "noticeable": 12, "significant": 14, "high": 6, "critical": 0},
    "by_shape": {"narrowband": 41, "chirp": 5, "wideband": 1},
    "by_band":  {"L1": 8, "L2": 28, "L5": 11},
    "peak_psd_dbmhz": -101.3,
    "peak_powerloss_dbm": -42.5
  },
  "pattern": {
    "sweep_detected": true,
    "events_in_window": 7,
    "distinct_minutes": 3,
    "cf_span_mhz": 18.24,
    "time_span_s": 120.0,
    "drift_mhz_per_s": 0.152
  },
  "events":     [ /* per-event with threat_level + shape */ ],
  "v1_active":  [ /* V1 events overlapping the window */ ],
  "v1_included": true
}

GET /ce — Cross-station correlated events (V2)

V2
GET /ce Cross-station correlated events

Detects groups of events with the same frequency and bandwidth across multiple stations within the specified interval (cross-station correlation).

Parameters
ParameterTypeDescription
start_datedatereqStart date
end_datedatereqEnd date
min_stationsintoptMinimum stations (default: 2)
Response
[
  {
    "centfreq_mhz": 1575.42,
    "bandwidth_mhz": 10.0,
    "station_count": 5,
    "stations": ["SULI", "BBUC", "DEVA", "TIMI", "ARAD"],
    "first_seen": "2026-05-26T12:00:00Z",
    "last_seen": "2026-05-26T14:30:00Z"
  },
  ...
]

GET /sa — Spoofing analysis (V2)

V2
GET /sa Spoofing analysis — 7-indicator engine

Returns the spoofing analysis for a station and interval. The engine scores 7 indicators: temporal, spatial, multi-band, spectral power, cross-station correlation, frequency pattern and temporal drift.

Parameters
ParameterTypeDescription
station_codestringreqStation code
start_datedatereqStart date
end_datedatereqEnd date
limitintoptMaximum rows returned (default 500, max 5000)
Response
[
  {
    "stationid": "SULI",
    "datetime": "2026-05-26T14:23:00Z",
    "centfreq_mhz": 1575.42,
    "threat_type": "spoofing_candidate",
    "confidence": 0.87,
    "severity": "HIGH",
    "temporal_score": 0.9,
    "spatial_score": 0.75,
    "multiband_score": 0.8,
    "psd_score": 0.85,
    "correlation_score": 0.7,
    "freq_pattern_score": 0.9,
    "drift_score": 0.65,
    "analysis_notes": "Strong temporal clustering with spatial spread..."
  },
  ...
]
Threat types
ValueDescription
spoofing_candidateSignal with spoofing characteristics (7 indicators active)
matched_spectrumSpectrum matching a known spoofing source
meaconingDelayed re-broadcast (meaconing)
cleanNo threat detected

GET /analytics — Aggregate statistics (V2)

V2
GET /analytics Aggregated statistics for dashboards

Aggregated statistics: top stations per constellation, frequency distribution, hour×day heatmap, BW/PL scatter, daily trend, weekday vs weekend.

Optional parameters
ParameterTypeDescription
daysintoptLookback days (default: 30)
V2
GET /trends Daily counts + correlation matrix

Daily event counts + station×station co-occurrence matrix (stations active on the same day).

POST /api/predict — ML prediction (V2)

V2
POST /api/predict ML interference prediction

Generates a 24h interference forecast for a station and a specific hour. Returns probability, hourly forecast, per-RF-band predictions and model info.

Body (JSON)
FieldTypeDescription
station_codestringreqStation code (e.g. "SULI")
datetimestringreqISO 8601 (e.g. "2026-05-26T14:00")
Example request
POST /api/predict
Content-Type: application/json

{
  "station_code": "SULI",
  "datetime": "2026-05-26T14:00"
}
Response
{
  "station_code": "SULI",
  "datetime": "2026-05-26T14:00:00",
  "probability": 73.0,
  // 0–100 scale (not 0–1)
  "expected_events": 45,
  "forecast_24h": [
    { "hour": 14, "probability": 73.0, "expected_events": 45 },
    { "hour": 15, "probability": 68.0, "expected_events": 38 },
    ...
  ],
  "band_predictions": [
    { "rfpath": "L1", "centfreq_mhz": 1575.42, "avg_bw": 8.2, "avg_pl": -42.1,
      "avg_psd": -94.3, "occurrences": 2841, "pct": 58.9 },
    { "rfpath": "L2", "centfreq_mhz": 1227.60, "avg_bw": 6.1, "avg_pl": -51.0,
      "avg_psd": -97.2, "occurrences": 1204, "pct": 24.9 },
    ...
  ],
  "model_info": {
    "model": "v1",
    "classifier_accuracy": 0.823,
    "classifier_auc": 0.909,
    "classifier_f1": 0.828,
    "training_samples": 549182,
    "trained_at": "2026-05-26T19:24:00Z"
  }
}

GET /api/predict/status

V2
GET /api/predict/status Model training status + accuracy

Current state of the LightGBM model: trained or training, accuracy, feature importance.

Response
{
  "trained": true,
  "is_training": false,
  "accuracy": {
    "classifier_accuracy": 0.823,
    "classifier_auc": 0.909,
    "classifier_f1": 0.828,
    "regressor_mae": 127,
    "regressor_r2": 0.81
  },
  "training_samples": 549182,
  "stations_with_baseline": 83,
  "training_time": 480.3,
  // seconds
  "feature_importance": {
    "hour": 0.312,
    "station_avg_psd": 0.198,
    "dow": 0.154,
    "baseline_eph": 0.121,
    "hour_sin": 0.089,
    ...
  },
  "trained_at": "2026-05-26T19:24:00Z"
}

GET /api/predict/compare

V2
GET /api/predict/compare Side-by-side LightGBM vs LSTM metrics

Side-by-side metrics for LightGBM and LSTM, with the winner per metric. If the LSTM is training, the is_training field is true and training_progress exposes the current step (e.g. "Epoch 32/60").

Response
{
  "lightgbm": {
    "trained": true,
    "is_training": false,
    "accuracy": {
      "classifier_accuracy": 0.823,
      "classifier_auc": 0.909,
      "classifier_f1": 0.828,
      "regressor_mae": 127,
      "regressor_r2": 0.81
    }
  },
  "lstm": {
    "trained": true,
    "is_training": false,
    "training_progress": null,
    "accuracy": {
      "classifier_accuracy": 0.919,
      "classifier_auc": 0.969,
      "classifier_f1": 0.811,
      "regressor_mae": 18.69
    }
  },
  "winner": {
    "accuracy": "lightgbm",
    "auc": "lightgbm",
    "f1": "lightgbm",
    "mae": "lstm"
  }
}

POST /api/predict/retrain

V2
POST /api/predict/retrain Force LightGBM retraining

Forces a retrain of the LightGBM model. The LSTM retraining is chained automatically after completion. Returns 409 if a training run is already in progress.

Body

No body required.

Response (success)
{ "status": "retraining started" }
Response (409 — already training)
{ "error": "Training already in progress" }

GET /i — Events (V1 Classic)

V1
GET /i Events by station name (legacy format)

Classic endpoint — returns events for a station with legacy field names. Used by the V1 polar chart.

Parameters
ParameterTypeDescription
statiestringreqStation name (e.g. Sulina)
start_datedatereqStart date (YYYY-MM-DD)
end_datedatereqEnd date (YYYY-MM-DD)
Response (V1 fields)
[
  {
    "DataOra": "2026-04-05T14:23:00",
    "DataOra_fin": "2026-04-05T14:45:00",
    "BandaRF": "L1",
    "Frecv_cent_MHz": 1575.42,
    "LunBanda_MHz": 10.0,
    "Pie_put_est_dBm": -42.5
  },
  ...
]

GET /stats — Systematic statistics (V1)

V1
GET /stats Systematic interference statistics

Aggregated per-RF-band statistics: occurrences, CF min/max, BW max, PL max, time range.

Parameters
ParameterTypeDescription
statiestringreqStation name
start_datedatereqStart date
end_datedatereqEnd date

GET /correlated — Correlated (V1)

V1
GET /correlated Cross-station correlation (V1 logic)

Events with the same frequency detected simultaneously across multiple stations (cross-station correlation). Different implementation from V2 /ce (kept for historical reference).

Parameters
ParameterTypeDescription
start_datedatereqStart date
end_datedatereqEnd date

GET /vadase — VADASE data (V1)

V1
GET /vadase VADASE displacement data

VADASE (Variometric Approach for Displacement Analysis Stand-alone Engine) displacement data for detecting GNSS station positional changes. Available for 74 ROMPOS stations with active VADASE licences. Useful for correlating interferences with positional displacements.

Parameters
ParameterTypeDescription
statiestringreqStation name
start_datedatereqStart date
end_datedatereqEnd date
Response
[
  {
    "DataOra": "2026-04-05T14:00:00",
    "East_mm": 2.3,
    "North_mm": -1.1,
    "Up_mm": 4.7
  },
  ...
]

RF Bands

BandFrequency (MHz)ConstellationDescription
L11575.42GPSPrimary GPS band, the most vulnerable to interference
L21227.60GPSSecondary GPS band, used for ionospheric corrections
L51176.45GPS / GalileoCivil safety-of-life band
E11575.42GalileoPrimary Galileo band (co-located with GPS L1)
GLONASS L11598.0625–1605.375GLONASSFDMA, channels 1–6
GLONASS L21242.9375–1248.625GLONASSSecondary FDMA

Error codes

HTTP codeMeaning
200Success
400Missing or invalid parameters
401API key required (protected endpoint, e.g. /flush)
404Station not found or no events
409Conflict (e.g. model already training)
429Too many requests — rate limit exceeded (see Rate limits)
500Internal server error (check logs)
503Service unavailable (model not trained)

Rate limits

Public endpoints are rate-limited per IP to protect the infrastructure. Applied limits:

EndpointLimit
/i, /ce, /sa, /lh/summary60 / minute
/lh, /now120 / minute
/analytics, /trends10 / minute
/api/predict, /api/predict/v120 / minute
/flush admin10 / hour
other endpoints200 / minute (default)

When the limit is hit, the API responds with HTTP 429 Too Many Requests and the body {"error": "Rate limit exceeded", "retry_after": "..."} — respect the retry hint before retrying.

For intensive queries (e.g. all stations daily), leave at least 500 ms between requests and prefer the aggregate endpoints (/analytics, /lh/summary) over individual per-station calls.

Examples

cURL — Prediction for Sulina

curl -X POST https://api.geoveil.ro/v2/api/predict \
  -H "Content-Type: application/json" \
  -d '{"station_code":"SULI","datetime":"2026-05-26T14:00"}'

Python — Last-hour events (specific station, limited)

import requests

resp = requests.get(
    "https://api.geoveil.ro/v2/lh",
    params={"station_code": "SULI", "limit": 3000}
)
events = resp.json()
for e in events:
    print(f"{e['datetime']}  {e['rfpath']}  {e['centfreq_mhz']} MHz")

JavaScript — Last-hour summary (all stations)

const resp = await fetch("https://api.geoveil.ro/v2/lh/summary");
const stations = await resp.json();
const threats = stations.filter(s => s.threat_type === "spoofing_candidate");
console.log(`${threats.length} stations with spoofing candidates in the last hour`);

JavaScript — Cross-station correlated

const today = new Date().toISOString().split('T')[0];
const resp = await fetch(
  `https://api.geoveil.ro/v2/ce?start_date=${today}&end_date=${today}&min_stations=3`
);
const correlated = await resp.json();
console.log(`${correlated.length} correlated event groups today`);

Python — V1 polar chart data

import requests

resp = requests.get(
  "https://api.geoveil.ro/v2/v1/i",
  params={"statie": "Sulina", "start_date": "2026-04-05", "end_date": "2026-04-05"}
)
events = resp.json()
for e in events:
    print(f"{e['DataOra']}  {e['BandaRF']}  {e['Frecv_cent_MHz']} MHz")