Zum Inhalt

API-Cookbook

Praktische Snippets für häufige Aufgaben.

1. Host anlegen + Agent-Token generieren

JWT="eyJhbGc..."
BASE="https://deine-domain.tld/api/v1"

# Host anlegen ("hostname" ist das Pflichtfeld, nicht "name" —
# der Worker korreliert Check-Ergebnisse über (tenant_id, hostname))
HOST=$(curl -s -X POST "$BASE/hosts/" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "hostname": "web03.acme.local",
    "tenant_id": "uuid-of-tenant",
    "profile_id": "uuid-of-linux-profile",
    "ip_address": "10.10.5.13",
    "monitoring_mode": "agent",
    "tags": ["production", "web"]
  }')

HOST_ID=$(echo "$HOST" | jq -r .id)

# Agent-Token generieren
TOKEN=$(curl -s -X POST "$BASE/hosts/$HOST_ID/agent-token" \
  -H "Authorization: Bearer $JWT" | jq -r .token)

# Agent installieren
ssh root@web03.acme.local \
  "wget -qO- https://deine-domain.tld/agent/install.sh | bash -s -- $TOKEN https://deine-domain.tld"
import requests

BASE = "https://deine-domain.tld/api/v1"
HEADERS = {"Authorization": f"Bearer {JWT}"}

# Host anlegen
r = requests.post(f"{BASE}/hosts/", json={
    "hostname": "web03.acme.local",
    "tenant_id": tenant_id,
    "profile_id": linux_profile_id,
    "ip_address": "10.10.5.13",
    "monitoring_mode": "agent",
    "tags": ["production", "web"],
}, headers=HEADERS)
host = r.json()

# Agent-Token
token = requests.post(
    f"{BASE}/hosts/{host['id']}/agent-token",
    headers=HEADERS,
).json()["token"]

print("Token:", token)

Die Antwort auf POST /hosts/ enthält zusätzlich skipped_checks: Checks, die das gewählte profile_id mitbringen würde, aber wegen des Modus nicht angelegt werden konnten (z. B. Agent-Check ohne monitoring_mode: "agent") — nie stiller Verlust, immer im Body sichtbar.

Statt eines lokalen profile_id akzeptiert der Endpoint auch community_id (ID eines Profils in der Vesana-Bibliothek, siehe Vesana-Bibliothek): der Server installiert es bei Bedarf automatisch und bindet den Host daran.

2. Bulk-Import aus CSV

import csv, requests

with open("hosts.csv") as f:
    for row in csv.DictReader(f):
        requests.post(f"{BASE}/hosts/", json={
            "hostname":   row["hostname"],
            "tenant_id":  row["tenant_id"],
            "profile_id": row["profile_id"],
            "ip_address": row["ip"],
        }, headers=HEADERS)

3. Service zu Host hinzufügen

curl -X POST "$BASE/host-services/" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "host_id": "host-uuid",
    "tenant_id": "tenant-uuid",
    "profile_check_id": "profile-check-uuid",
    "display_name": "Disk /var",
    "config_overrides": { "path": "/var" },
    "interval_override": 60
  }'

profile_check_id (aus einem Geräteprofil) und check_type (freier Check ohne Profil, „Inline-Check" mit config_overrides als komplette Check-Config) schließen sich gegenseitig aus — genau eines von beiden angeben.

4. Dashboard programmatisch anlegen

Das Dashboard gehört dem Benutzer, dessen Token die Anfrage stellt — nicht einem Tenant; ein tenant_id im Body wird ignoriert. Ist der Titel schon belegt, hängt Vesana eine Zahl an („Acme NOC 2").

Die API übernimmt exakt die config-Struktur, die die Web-App schreibt. Am einfachsten baust du ein Dashboard einmal in der Web-App, liest es mit GET /dashboards/{id} und verwendest dessen config als Vorlage (oder kopierst es mit POST /dashboards/{id}/duplicate). Minimalbeispiel mit einer Tabelle der Checks eines Tenants:

dashboard = requests.post(f"{BASE}/dashboards/", json={
    "title": "Acme NOC",
    "config": {
        "schemaVersion": 2,
        "timeSettings": { "from": "now-24h", "to": "now", "refreshInterval": 0 },
        "widgets": {
            "w1": {
                "type": "table",
                "title": "Checks Acme",
                "dataSource": { "type": "status_table", "tenant_ids": [tenant_id] },
                "options": {}
            }
        },
        "layout": {
            "lg": [
                { "i": "w1", "x": 0, "y": 0, "w": 24, "h": 10 }
            ]
        }
    }
}, headers=HEADERS).json()

Das Raster ist 24 Spalten breit. Widget-Typen: summary_state, summary_problems, summary_devices, summary_inventory, summary_activity, summary_noisiest, summary_tenants, stat, gauge, line_chart, bar_chart, top_n, table, heatmap, check_history, status_timeline, log_stream, system_log, anomaly_list, prediction_list, text, link_button. Ältere Typnamen (status_overview, pie_chart, host_grid) werden beim Lesen auf die heutigen abgebildet.

5. Bulk-Tag-Update

GET /hosts/ liefert ein JSON-Array (seitenweise, höchstens 500 je Anfrage) und hat keinen Tag-Filter — gefiltert wird im Script:

offset = 0
while True:
    page = requests.get(f"{BASE}/hosts/", params={"limit": 500, "offset": offset}, headers=HEADERS).json()
    for h in page:
        if "legacy" in h["tags"]:
            new_tags = [t for t in h["tags"] if t != "legacy"] + ["deprecated"]
            requests.patch(f"{BASE}/hosts/{h['id']}", json={"tags": new_tags}, headers=HEADERS)
    if len(page) < 500:
        break
    offset += 500

6. Webhook-Empfänger absichern

Der Kanal-Typ Webhook schickt einen JSON-POST ohne Signatur und ohne eigene Header. Zur Absicherung hängst du ein Geheimnis an die Webhook-URL des Kanals (z. B. https://tickets.example.com/vesana-webhook?token=…) und prüfst es im Empfänger:

import hmac
from flask import Flask, request

WEBHOOK_TOKEN = "<Token aus der Webhook-URL des Kanals>"

app = Flask(__name__)

@app.route("/vesana-webhook", methods=["POST"])
def receive():
    if not hmac.compare_digest(request.args.get("token", ""), WEBHOOK_TOKEN):
        return "forbidden", 403

    payload = request.get_json()
    if payload["status"] == "CRITICAL":
        send_pager(payload["host_name"], payload["service_name"])
    return "ok", 200

Felder im Body: type, host_name, host_ip, service_name, status, previous_status, message, triggered_at, tenant_name, dashboard_url, dazu je nach Ereignis duration_seconds und extra_data.

7. SLA-Report ziehen

Gehört zur Vorschau-Funktion „Reports (PDF + SLA)" — ohne Freischaltung antwortet der Endpoint mit 404.

curl "$BASE/sla/tenants/acme-uuid/sla-report?start=2026-04-01T00:00:00Z&end=2026-05-01T00:00:00Z" \
  -H "Authorization: Bearer $JWT" \
  | jq '.'

8. Wartungsfenster setzen + auflösen

# Wartung für einen Host planen
curl -X POST "$BASE/downtimes/" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "host_id": "host-uuid",
    "start_at": "2026-04-30T22:00:00Z",
    "end_at":   "2026-04-30T23:30:00Z",
    "comment":  "Kernel-Update"
  }'

# Wiederkehrend (RRULE) statt einmalig: zusätzlich "recurrence" setzen,
# z. B. "FREQ=WEEKLY;BYDAY=MO,WE;UNTIL=20260801T220000Z"

# Für mehrere Checks auf einmal: POST $BASE/downtimes/bulk
# mit { "service_ids": [...], "start_at": ..., "end_at": ... }

# Vorzeitig beenden
curl -X DELETE "$BASE/downtimes/<id>" \
  -H "Authorization: Bearer $JWT"

9. AI-Service-Analyse abrufen

curl -X POST "$BASE/ai/analyze/<service_id>" \
  -H "Authorization: Bearer $JWT"

10. Audit-Log für Compliance exportieren

GET /audit/ liefert JSON (höchstens 500 Einträge je Anfrage, Gesamtzahl in X-Total-Count). Für eine CSV-Datei seitenweise abholen und umwandeln:

OFFSET=0
while :; do
  PAGE=$(curl -s "$BASE/audit/?created_from=2026-04-01T00:00:00Z&created_to=2026-05-01T00:00:00Z&limit=500&offset=$OFFSET" \
    -H "Authorization: Bearer $JWT")
  echo "$PAGE" | jq -r '.[] | [.created_at, .actor_email, .action, .target_type, .target_id] | @csv'
  [ "$(echo "$PAGE" | jq length)" -lt 500 ] && break
  OFFSET=$((OFFSET + 500))
done > audit-april.csv

Anschluss