Why estwarden?

Estonia and the Baltic states sit at a geopolitical fault line. Between NATO's eastern flank and Russia's Western Military District, the security environment shifts daily - troop rotations, hybrid operations, diplomatic signals, and infrastructure threats form a complex, fast-moving picture.

Most people don't have time to read defense ministry press releases, track OSINT Twitter accounts, or cross-reference naval movements with diplomatic rhetoric. estwarden exists to close that gap.

It's an observability tool for geopolitical risk - the same concept as monitoring your servers with Grafana, but applied to real-world security indicators. Every day at 08:00 EET, it queries multiple intelligence sources via AI, extracts structured threat indicators, assigns severity levels, and publishes the result as a dashboard, API, and Telegram briefing.

Think of it as a daily pulse check: means quiet, means something is developing, means pay attention, means immediate concern.

estwarden is automated OSINT - it augments, not replaces, professional intelligence analysis. Indicators may contain errors or miss relevant events. Use it as a starting point, not a sole source of truth.

💬 Telegram

The easiest way to stay informed. Join the public channel and receive a daily infographic briefing at 08:00 EET with the current threat level, all indicators, and a summary.

→ @estonia_safety_monitor

No API keys, no setup, no infrastructure. Just open Telegram.

🖥️ Bash + Cron

Poll the estwarden API from any server and act on the result. This script checks the current threat level and sends a notification if it's not GREEN.

estwarden-check.sh
#!/bin/bash
# estwarden daily check - add to crontab
# 0 9 * * * /usr/local/bin/estwarden-check.sh

API="https://estwarden.eu/api/latest"
NOTIFY_EMAIL="you@example.com"  # or use ntfy/gotify/pushover

DATA=$(curl -sf "$API")
if [ $? -ne 0 ]; then
  echo "estwarden API unreachable" | mail -s "⚠️ estwarden down" "$NOTIFY_EMAIL"
  exit 1
fi

LEVEL=$(echo "$DATA" | jq -r '.threat_level')
DATE=$(echo "$DATA" | jq -r '.date')
COUNT=$(echo "$DATA" | jq '.indicators | length')

if [ "$LEVEL" != "GREEN" ]; then
  SUMMARY=$(echo "$DATA" | jq -r '
    .indicators[]
    | select(.status != "GREEN")
    | "  \(.status) [\(.category)] \(.label): \(.finding)"
  ')

  cat <<EOF | mail -s "🔴 estwarden: $LEVEL ($DATE)" "$NOTIFY_EMAIL"
Threat level: $LEVEL
Date: $DATE
Active indicators: $COUNT

Non-green indicators:
$SUMMARY

Dashboard: https://estwarden.eu
EOF
fi

Requires curl, jq, and a mail sender. Swap mail for curl to ntfy.sh, Gotify, Pushover, or any webhook.

📊 Grafana

Add estwarden to your monitoring stack using the Infinity datasource plugin (JSON/REST). This gives you threat level panels, indicator tables, and alerting - right next to your infrastructure metrics.

1. Install the Infinity datasource

Grafana CLI
grafana-cli plugins install yesoreyeram-infinity-datasource
systemctl restart grafana-server

2. Add the datasource

Go to Connections → Data sources → Add → Infinity. Name it estwarden. No auth required - the API is public.

3. Create panels

Stat panel - Current threat level:

Infinity query
Type:    JSON
URL:     https://estwarden.eu/api/latest
Method:  GET
Parser:  Backend

Root:    (leave blank)
Columns:
  threat_level  →  String
  date          →  String

Table panel - All indicators:

Infinity query
Type:    JSON
URL:     https://estwarden.eu/api/latest
Method:  GET
Parser:  Backend

Root:    indicators
Columns:
  status    →  String
  category  →  String
  label     →  String
  finding   →  String

Time series - Threat history:

Infinity query
Type:    JSON
URL:     https://estwarden.eu/api/history?days=90
Method:  GET
Parser:  Backend

Root:    (array)
Columns:
  date       →  Timestamp
  score      →  Number
  red_count  →  Number
  yellow_count → Number
  green_count  → Number

4. Add alerting

Create an alert rule on the threat level panel: threat_level != "GREEN" → sends to your notification channel (Slack, Telegram, email, PagerDuty).

🏠 Home Assistant

Add estwarden as a REST sensor to show the Baltic threat level on your HA dashboard, trigger automations, or change your smart home behavior based on security conditions.

configuration.yaml

YAML
rest:
  - resource: https://estwarden.eu/api/latest
    scan_interval: 3600
    sensor:
      - name: "Baltic Threat Level"
        value_template: "{{ value_json.threat_level }}"
        icon: mdi:shield-alert
        json_attributes:
          - date
          - summary

      - name: "Baltic Threat Score"
        value_template: "{{ value_json.indicators | length }}"
        unit_of_measurement: "indicators"
        icon: mdi:counter

      - name: "Baltic Red Indicators"
        value_template: >-
          {{ value_json.indicators
             | selectattr('status', 'eq', 'RED')
             | list | count }}
        icon: mdi:alert-circle

Automation example

automations.yaml
- alias: "Baltic threat alert"
  trigger:
    - platform: state
      entity_id: sensor.baltic_threat_level
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state in ['ORANGE', 'RED'] }}"
  action:
    - service: notify.mobile_app
      data:
        title: "⚠️ Baltic Threat: {{ states('sensor.baltic_threat_level') }}"
        message: >-
          {{ state_attr('sensor.baltic_threat_level', 'summary') }}
        data:
          url: https://estwarden.eu

⚡ n8n

Build automated workflows that react to threat level changes. Use the HTTP Request node to poll the API and route to any action - Slack, email, database, webhook, or custom logic.

Workflow: daily check with conditional alert

n8n nodes
┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│   Schedule   │───▶│ HTTP Request │───▶│   IF Node    │───▶│    Slack     │
│  (daily 9am) │    │  GET /latest │    │ level≠GREEN  │    │  #security   │
└──────────────┘    └──────────────┘    └──────┬───────┘    └──────────────┘
                                               │ false
                                               ▼
                                          (no action)

HTTP Request node config

n8n settings
Method:          GET
URL:             https://estwarden.eu/api/latest
Response Format: JSON
Authentication:  None

IF node condition

Expression
{{ $json.threat_level }} is not equal to GREEN

You can extend this with a Code node to format the indicators, filter by category, or compare with yesterday's level using the /api/history endpoint.

🔗 API Reference

All endpoints return JSON. No authentication required. CORS is open.

MethodEndpointDescription
GET /api/latest Most recent report with all indicators
GET /api/today Today's report (404 if not yet collected)
GET /api/report/{date} Report for a specific date (YYYY-MM-DD)
GET /api/history?days=90 Threat level history (max 365 days)
GET /api/categories?days=90 Per-category breakdown over time
GET /api/dates All available report dates
GET /health Service health + next scheduled run
Economy
GET /api/metrics Latest socioeconomic metrics (inflation, sentiment, cost of living)

Example response - /api/latest

JSON
{
  "date": "2026-03-05",
  "threat_level": "GREEN",
  "summary": "No significant security developments...",
  "indicators": [
    {
      "status": "GREEN",
      "category": "MILITARY",
      "label": "6th CAA",
      "finding": "No activity in last 24h",
      "source_url": "https://..."
    }
  ]
}

Example response - /api/metrics

JSON
{
  "date": "2026-03-05",
  "summary": "Estonia's economy is cautiously recovering...",
  "metrics": [
    {
      "category": "INFLATION",
      "label": "CPI YoY Inflation",
      "value": "4.00%",
      "trend": "DOWN",
      "finding": "Inflation slowed to 4.0% in early 2026",
      "source_url": "https://..."
    },
    {
      "category": "SENTIMENT",
      "label": "Russian-Speaking Community Sentiment",
      "value": "TENSE",
      "trend": "STABLE",
      "finding": "Integration tensions persist in Ida-Viru",
      "source_url": "https://..."
    }
  ]
}