{
    "openapi": "3.1.0",
    "info": {
        "title": "cronjob.de REST API",
        "version": "1.0.0",
        "description": "Manage cron jobs, heartbeats, server monitors and status pages on cronjob.de. Same functions as the MCP server at https://www.cronjob.de/bridge. Reading needs an API key; changes need the Unlimited plan (or the trial). Docs: https://www.cronjob.de/api"
    },
    "servers": [
        {
            "url": "https://www.cronjob.de/api/v1"
        }
    ],
    "security": [
        {
            "bearer": []
        }
    ],
    "components": {
        "securitySchemes": {
            "bearer": {
                "type": "http",
                "scheme": "bearer",
                "description": "API key \"<user_id>:<token>\" from https://www.cronjob.de/loggedin/ki-zugang#ohne-browser"
            }
        }
    },
    "paths": {
        "/jobs": {
            "get": {
                "operationId": "list_jobs",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjobs auflisten",
                "description": "Listet alle Cronjobs des Nutzers mit dekodiertem Zeitplan sowie Methode, Body, Kopfzeilen und Basic-Auth-Benutzer. type unterscheidet Cronjobs, die Cronjob.de aufruft (\"http\"), von Heartbeats (\"heartbeat\": der Job läuft woanders und meldet sich unter ping_url, status nennt den Zustand). Cronjobs mit Endsignal tragen end_signal_minutes, end_signal_url und end_signal_state.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            },
            "post": {
                "operationId": "create_job",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob anlegen",
                "description": "Legt einen neuen Cronjob an: Cronjob.de ruft die URL nach Zeitplan auf. Zeitplan entweder als schedule-Objekt oder als cron-Ausdruck (eines von beiden ist Pflicht). Ohne Angabe wird die URL per GET aufgerufen. Mit end_signal_minutes meldet das aufgerufene Skript zusätzlich sein Ende (Antwort mit end_signal_url und snippets). Liefert den angelegten Job zurück. Für Jobs, die woanders laufen und nur überwacht werden sollen: create_heartbeat. Um eine Website, einen Server oder eine API auf Erreichbarkeit zu überwachen: create_monitor (Serverüberwachung), NICHT create_job.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Name des Jobs (max. 255 Zeichen)."
                                    },
                                    "url": {
                                        "type": "string",
                                        "description": "Aufzurufende URL, muss mit http:// oder https:// beginnen."
                                    },
                                    "schedule": {
                                        "type": "object",
                                        "description": "Zeitplan als Objekt mit Zahlen-Listen: minute (0-59), hour (0-23), day (1-31), month (1-12), weekday (0=Sonntag … 6=Samstag). Jede Liste muss mindestens einen Wert haben. Beispiel täglich 06:30: {\"minute\":[30],\"hour\":[6],\"day\":[1..31],\"month\":[1..12],\"weekday\":[0..6]}. Wichtig: Sind day UND weekday eingeschränkt, müssen BEIDE zutreffen (UND), nicht eines von beiden wie bei Unix-cron. Das ist ein Merkmal: day 8-14 plus weekday 3 ergibt genau den zweiten Mittwoch im Monat, day 25-31 plus weekday 1 den letzten Montag. Wer nur Wochentage meint, setzt day auf alle Werte 1-31; wer nur Monatstage meint, weekday auf 0-6. Alle Uhrzeiten gelten in deutscher Zeit (Europe/Berlin, MEZ/MESZ); Zeiten in UTC oder einer anderen Zeitzone vorher umrechnen.",
                                        "properties": {
                                            "minute": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "hour": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "day": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "month": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "weekday": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            }
                                        },
                                        "required": [
                                            "minute",
                                            "hour",
                                            "day",
                                            "month",
                                            "weekday"
                                        ]
                                    },
                                    "cron": {
                                        "type": "string",
                                        "description": "Alternative zu schedule: klassischer Cron-Ausdruck mit 5 Feldern \"min std tag monat wochentag\", z. B. \"*/5 * * * *\" (alle 5 Minuten) oder \"30 6 * * 1-5\" (Mo-Fr 06:30). Unterstützt *, Listen (1,15), Bereiche (1-5), Schritte (*/10, 1-30/5), Wochentag 0 oder 7 = Sonntag, sowie @hourly, @daily, @weekly, @monthly, @yearly. Hinweis: Sind Tag UND Wochentag eingeschränkt, müssen hier BEIDE zutreffen (UND), nicht eines von beiden wie bei Unix-cron. Damit geht \"0 3 8-14 * 3\" = jeder zweite Mittwoch im Monat, was klassisches cron nur mit einem Shell-Test kann. Ein aus einer bestehenden Crontab übernommener Ausdruck läuft hier deshalb seltener als dort. Uhrzeiten gelten in deutscher Zeit (Europe/Berlin, MEZ/MESZ)."
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "description": "Job aktiv? Standard: true."
                                    },
                                    "folder_id": {
                                        "type": "integer",
                                        "description": "Ordner-ID (0 = unsortiert). Alternativ folder_name."
                                    },
                                    "folder_name": {
                                        "type": "string",
                                        "description": "Ordnername; wird angelegt, falls nicht vorhanden."
                                    },
                                    "end_signal_minutes": {
                                        "type": "integer",
                                        "description": "Endsignal für einen Cronjob, den Cronjob.de aufruft: Das aufgerufene Skript meldet am Ende unter end_signal_url, dass es durchgelaufen ist. Kommt diese Meldung nicht innerhalb von end_signal_minutes nach dem Aufruf, warnt Cronjob.de – so fällt auch ein Skript auf, das mit Status 200 antwortet, aber vorzeitig abbricht. 1 bis 1440 schaltet ein (Frist in Minuten), 0 schaltet aus. Ab Unlimited. Für Jobs, die woanders laufen, ist create_heartbeat richtig."
                                    },
                                    "method": {
                                        "type": "string",
                                        "enum": [
                                            "GET",
                                            "POST",
                                            "PUT",
                                            "PATCH",
                                            "DELETE",
                                            "HEAD"
                                        ],
                                        "description": "HTTP-Methode. Standard GET."
                                    },
                                    "body": {
                                        "type": "string",
                                        "description": "Anfrage-Body. Nur bei POST, PUT, PATCH und DELETE."
                                    },
                                    "content_type": {
                                        "type": "string",
                                        "description": "Content-Type des Bodys, etwa application/json. Höchstens 120 Zeichen."
                                    },
                                    "headers": {
                                        "type": "array",
                                        "description": "Eigene Kopfzeilen, höchstens 20. Host, Content-Length, Transfer-Encoding und Connection sind gesperrt.",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "name": {
                                                    "type": "string"
                                                },
                                                "value": {
                                                    "type": "string"
                                                }
                                            },
                                            "required": [
                                                "name",
                                                "value"
                                            ]
                                        }
                                    },
                                    "auth_user": {
                                        "type": "string",
                                        "description": "Benutzername für HTTP-Basic-Auth."
                                    },
                                    "auth_password": {
                                        "type": "string",
                                        "description": "Passwort für HTTP-Basic-Auth. Nur schreibbar; gelesen wird nur auth_password_set."
                                    },
                                    "expect_contains": {
                                        "type": "string",
                                        "description": "Antwortprüfung: Text, der in der Antwort stehen muss (z. B. \"OK\"), sonst gilt der Lauf als fehlgeschlagen. Höchstens 100 Zeichen, Groß-/Kleinschreibung spielt keine Rolle. Geprüft wird die ganze Antwort bis 1 MB, nur bei Status 2xx/3xx. Leerer String schaltet die Prüfung aus. Ab Unlimited."
                                    },
                                    "expect_not_contains": {
                                        "type": "string",
                                        "description": "Antwortprüfung: Text, der NICHT in der Antwort stehen darf (z. B. \"FEHLER\"). Höchstens 100 Zeichen, Groß-/Kleinschreibung spielt keine Rolle. Leerer String schaltet aus. Ab Unlimited."
                                    },
                                    "detect_errors": {
                                        "type": "boolean",
                                        "description": "Antwortprüfung: typische Fehlermeldungen als Fehler werten, ohne Rücksicht auf Groß-/Kleinschreibung (PHP \"Fatal error\", \"Parse error\", \"Uncaught Exception/Error\", Python \"Traceback (most recent call last)\", Java \"Exception in thread\", Node \"UnhandledPromiseRejection\", WordPress \"kritischen Fehler auf\" / \"There has been a critical error on\", Laravel \"Whoops, looks like something went wrong\")."
                                    },
                                    "notify_after_failures": {
                                        "type": "integer",
                                        "description": "Wann melden: 1 = beim ersten Fehlschlag (Standard), 2 bis 10 = erst nach so vielen Fehlschlägen in Folge, 0 = nie (Fehler werden nur protokolliert). Ab Plus."
                                    },
                                    "retries": {
                                        "type": "integer",
                                        "description": "Bei Aussetzern wiederholen: 0 = aus (Standard), 1 bis 3 weitere Versuche kurz nacheinander, nur bei Fehlern, bei denen der Aufruf das Skript nicht erreicht hat: Verbindung abgelehnt, TLS-Verbindung gescheitert, HTTP 408, 429, 502, 503, 521, 522, 523, 525. Nie nach 500, 504, 524, Zeitüberschreitung oder Weiterleitung. Die Warnung wartet die Wiederholungen ab. Ab Plus."
                                    },
                                    "expires_at": {
                                        "type": "string",
                                        "description": "Ablaufdatum: ab dann ist der Cronjob aus (Zeitplan und Protokoll bleiben). Format \"YYYY-MM-DD HH:MM\", deutsche Zeit, muss in der Zukunft liegen. Leerer String entfernt das Ablaufdatum. Einmaliger Lauf (z. B. nur am 31.12.2026 um 8 Uhr): Zeitplan mit Tag und Monat (0 8 31 12 *) und expires_at auf 0:00 Uhr am Folgetag. Enddatum (bis zum 15.11.): normaler Zeitplan und expires_at auf 0:00 Uhr am Tag danach."
                                    }
                                },
                                "required": [
                                    "name",
                                    "url"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/jobs/{job_id}": {
            "get": {
                "operationId": "get_job",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob ansehen",
                "description": "Details zu einem Cronjob inkl. dekodiertem Zeitplan, nächster Ausführung, den erweiterten Aufrufeinstellungen (method, body, content_type, headers, auth_user, auth_password_set), der Antwortprüfung (expect_contains, expect_not_contains, detect_errors; response_check_paused nennt die Teile, die gespeichert sind, im aktuellen Tarif aber ruhen und nach einem Upgrade wieder gelten) und (Tarif U) run_stats: Dauer, Bytes und Fehlerflag der letzten bis zu 200 Läufe. Endsignal: end_signal_minutes (0 = aus), end_signal_url und end_signal_state (ok, waiting = noch keine Meldung, running = Frist läuft, missed = Ende ausgeblieben, failed = /fail gemeldet, paused = Job aus oder im Tarif nicht enthalten). Heartbeats (type \"heartbeat\"): ping_url, rhythm_minutes und grace_minutes (rhythm_minutes null = genauer Zeitplan, grace_minutes gilt dann ab dem Termin), status (waiting_for_first_ping, ok, running, missed, failed, stuck, paused) und expected_by: bis dahin muss die nächste Meldung da sein.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            },
            "patch": {
                "operationId": "update_job",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob ändern",
                "description": "Ändert einen bestehenden Cronjob oder Heartbeat. Nur übergebene Felder werden geändert. headers ersetzt immer die komplette Liste; eine leere Liste entfernt alle Kopfzeilen. Liefert den Job danach zurück. Endsignal (\"überwache, ob mein Cronjob bis zum Ende läuft\"): end_signal_minutes bei einem Cronjob, den Cronjob.de aufruft – die Antwort enthält dann end_signal_url und snippets mit der Zeile für das Ende des Skripts. Bei Heartbeats (type \"heartbeat\") änderbar: name, active, rhythm_minutes und grace_minutes, oder ein genauer Zeitplan (cron oder schedule) mit grace_minutes.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "url": {
                                        "type": "string"
                                    },
                                    "schedule": {
                                        "type": "object",
                                        "description": "Zeitplan als Objekt mit Zahlen-Listen: minute (0-59), hour (0-23), day (1-31), month (1-12), weekday (0=Sonntag … 6=Samstag). Jede Liste muss mindestens einen Wert haben. Beispiel täglich 06:30: {\"minute\":[30],\"hour\":[6],\"day\":[1..31],\"month\":[1..12],\"weekday\":[0..6]}. Wichtig: Sind day UND weekday eingeschränkt, müssen BEIDE zutreffen (UND), nicht eines von beiden wie bei Unix-cron. Das ist ein Merkmal: day 8-14 plus weekday 3 ergibt genau den zweiten Mittwoch im Monat, day 25-31 plus weekday 1 den letzten Montag. Wer nur Wochentage meint, setzt day auf alle Werte 1-31; wer nur Monatstage meint, weekday auf 0-6. Alle Uhrzeiten gelten in deutscher Zeit (Europe/Berlin, MEZ/MESZ); Zeiten in UTC oder einer anderen Zeitzone vorher umrechnen.",
                                        "properties": {
                                            "minute": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "hour": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "day": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "month": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            },
                                            "weekday": {
                                                "type": "array",
                                                "items": {
                                                    "type": "integer"
                                                }
                                            }
                                        },
                                        "required": [
                                            "minute",
                                            "hour",
                                            "day",
                                            "month",
                                            "weekday"
                                        ]
                                    },
                                    "cron": {
                                        "type": "string",
                                        "description": "Alternative zu schedule: klassischer Cron-Ausdruck mit 5 Feldern \"min std tag monat wochentag\", z. B. \"*/5 * * * *\" (alle 5 Minuten) oder \"30 6 * * 1-5\" (Mo-Fr 06:30). Unterstützt *, Listen (1,15), Bereiche (1-5), Schritte (*/10, 1-30/5), Wochentag 0 oder 7 = Sonntag, sowie @hourly, @daily, @weekly, @monthly, @yearly. Hinweis: Sind Tag UND Wochentag eingeschränkt, müssen hier BEIDE zutreffen (UND), nicht eines von beiden wie bei Unix-cron. Damit geht \"0 3 8-14 * 3\" = jeder zweite Mittwoch im Monat, was klassisches cron nur mit einem Shell-Test kann. Ein aus einer bestehenden Crontab übernommener Ausdruck läuft hier deshalb seltener als dort. Uhrzeiten gelten in deutscher Zeit (Europe/Berlin, MEZ/MESZ)."
                                    },
                                    "active": {
                                        "type": "boolean"
                                    },
                                    "folder_id": {
                                        "type": "integer"
                                    },
                                    "folder_name": {
                                        "type": "string"
                                    },
                                    "end_signal_minutes": {
                                        "type": "integer",
                                        "description": "Endsignal für einen Cronjob, den Cronjob.de aufruft: Das aufgerufene Skript meldet am Ende unter end_signal_url, dass es durchgelaufen ist. Kommt diese Meldung nicht innerhalb von end_signal_minutes nach dem Aufruf, warnt Cronjob.de – so fällt auch ein Skript auf, das mit Status 200 antwortet, aber vorzeitig abbricht. 1 bis 1440 schaltet ein (Frist in Minuten), 0 schaltet aus. Ab Unlimited. Für Jobs, die woanders laufen, ist create_heartbeat richtig."
                                    },
                                    "rhythm_minutes": {
                                        "type": "integer",
                                        "description": "Nur Heartbeats: Rhythmus in Minuten: so oft meldet sich der Job mindestens (1 bis 20160 = 14 Tage), z. B. 60 = stündlich, 1440 = täglich. Gewarnt wird, wenn nach der letzten Meldung rhythm_minutes plus grace_minutes vergehen, ohne dass eine neue kommt – egal, zu welcher Uhrzeit der Job läuft. Nicht zusammen mit schedule."
                                    },
                                    "grace_minutes": {
                                        "type": "integer",
                                        "description": "Nur Heartbeats: Spielraum in Minuten (1 bis 10080 = 7 Tage): so viel später als erwartet darf die Meldung kommen. Ohne Angabe beim Rhythmus 1 bis 15 Minuten je nach Rhythmus, beim Zeitplan 15 Minuten. Für Jobs mit schwankender Laufzeit großzügiger wählen."
                                    },
                                    "method": {
                                        "type": "string",
                                        "enum": [
                                            "GET",
                                            "POST",
                                            "PUT",
                                            "PATCH",
                                            "DELETE",
                                            "HEAD"
                                        ],
                                        "description": "HTTP-Methode. Standard GET."
                                    },
                                    "body": {
                                        "type": "string",
                                        "description": "Anfrage-Body. Nur bei POST, PUT, PATCH und DELETE."
                                    },
                                    "content_type": {
                                        "type": "string",
                                        "description": "Content-Type des Bodys, etwa application/json. Höchstens 120 Zeichen."
                                    },
                                    "headers": {
                                        "type": "array",
                                        "description": "Eigene Kopfzeilen, höchstens 20. Host, Content-Length, Transfer-Encoding und Connection sind gesperrt.",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "name": {
                                                    "type": "string"
                                                },
                                                "value": {
                                                    "type": "string"
                                                }
                                            },
                                            "required": [
                                                "name",
                                                "value"
                                            ]
                                        }
                                    },
                                    "auth_user": {
                                        "type": "string",
                                        "description": "Benutzername für HTTP-Basic-Auth."
                                    },
                                    "auth_password": {
                                        "type": "string",
                                        "description": "Passwort für HTTP-Basic-Auth. Nur schreibbar; gelesen wird nur auth_password_set."
                                    },
                                    "expect_contains": {
                                        "type": "string",
                                        "description": "Antwortprüfung: Text, der in der Antwort stehen muss (z. B. \"OK\"), sonst gilt der Lauf als fehlgeschlagen. Höchstens 100 Zeichen, Groß-/Kleinschreibung spielt keine Rolle. Geprüft wird die ganze Antwort bis 1 MB, nur bei Status 2xx/3xx. Leerer String schaltet die Prüfung aus. Ab Unlimited."
                                    },
                                    "expect_not_contains": {
                                        "type": "string",
                                        "description": "Antwortprüfung: Text, der NICHT in der Antwort stehen darf (z. B. \"FEHLER\"). Höchstens 100 Zeichen, Groß-/Kleinschreibung spielt keine Rolle. Leerer String schaltet aus. Ab Unlimited."
                                    },
                                    "detect_errors": {
                                        "type": "boolean",
                                        "description": "Antwortprüfung: typische Fehlermeldungen als Fehler werten, ohne Rücksicht auf Groß-/Kleinschreibung (PHP \"Fatal error\", \"Parse error\", \"Uncaught Exception/Error\", Python \"Traceback (most recent call last)\", Java \"Exception in thread\", Node \"UnhandledPromiseRejection\", WordPress \"kritischen Fehler auf\" / \"There has been a critical error on\", Laravel \"Whoops, looks like something went wrong\")."
                                    },
                                    "notify_after_failures": {
                                        "type": "integer",
                                        "description": "Wann melden: 1 = beim ersten Fehlschlag (Standard), 2 bis 10 = erst nach so vielen Fehlschlägen in Folge, 0 = nie (Fehler werden nur protokolliert). Ab Plus."
                                    },
                                    "retries": {
                                        "type": "integer",
                                        "description": "Bei Aussetzern wiederholen: 0 = aus (Standard), 1 bis 3 weitere Versuche kurz nacheinander, nur bei Fehlern, bei denen der Aufruf das Skript nicht erreicht hat: Verbindung abgelehnt, TLS-Verbindung gescheitert, HTTP 408, 429, 502, 503, 521, 522, 523, 525. Nie nach 500, 504, 524, Zeitüberschreitung oder Weiterleitung. Die Warnung wartet die Wiederholungen ab. Ab Plus."
                                    },
                                    "expires_at": {
                                        "type": "string",
                                        "description": "Ablaufdatum: ab dann ist der Cronjob aus (Zeitplan und Protokoll bleiben). Format \"YYYY-MM-DD HH:MM\", deutsche Zeit, muss in der Zukunft liegen. Leerer String entfernt das Ablaufdatum. Einmaliger Lauf (z. B. nur am 31.12.2026 um 8 Uhr): Zeitplan mit Tag und Monat (0 8 31 12 *) und expires_at auf 0:00 Uhr am Folgetag. Enddatum (bis zum 15.11.): normaler Zeitplan und expires_at auf 0:00 Uhr am Tag danach."
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            },
            "delete": {
                "operationId": "delete_job",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob löschen",
                "description": "Löscht einen Cronjob oder Heartbeat samt Verlauf endgültig. Zur Sicherheit muss confirm_name exakt dem Namen entsprechen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "confirm_name": {
                                        "type": "string",
                                        "description": "Exakter Name des zu löschenden Jobs."
                                    }
                                },
                                "required": [
                                    "confirm_name"
                                ]
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/jobs/{job_id}/history": {
            "get": {
                "operationId": "list_job_history",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Aufrufprotokoll lesen",
                "description": "Die letzten 20 Ausführungen eines Jobs (Zeitpunkt, Fehlerstatus, Ausgabe).",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            }
        },
        "/jobs/{job_id}/timeline": {
            "get": {
                "operationId": "get_job_timeline",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Verlauf der letzten Wochen",
                "description": "Verlauf eines Jobs über die letzten 30 Tage (Tarif Plus) bzw. 90 Tage (Unlimited): je Tag Anzahl der Aufrufe, davon fehlgeschlagen, Laufzeit im Schnitt und höchstens, dazu die Fehlerzeiträume (gleiche Fehler hintereinander zusammengefasst, mit Einordnung und Rat). recorded_since nennt den ersten aufgezeichneten Tag. Für einzelne Läufe mit Ausgabe: list_job_history.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            }
        },
        "/jobs/{job_id}/active": {
            "put": {
                "operationId": "set_job_active",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob an- oder abschalten",
                "description": "Aktiviert oder deaktiviert einen Cronjob oder Heartbeat. Einen pausierten Heartbeat einschalten geht, solange die Tarifgrenze für gleichzeitig aktive Heartbeats nicht erreicht ist.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "active": {
                                        "type": "boolean"
                                    }
                                },
                                "required": [
                                    "active"
                                ]
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/jobs/{job_id}/move": {
            "post": {
                "operationId": "move_job",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob verschieben",
                "description": "Verschiebt einen Cronjob in einen anderen Ordner (folder_id 0 = unsortiert, oder folder_name).",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "folder_id": {
                                        "type": "integer"
                                    },
                                    "folder_name": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/jobs/{job_id}/run": {
            "post": {
                "operationId": "run_job_now",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Cronjob jetzt ausführen",
                "description": "Ruft einen Cronjob sofort einmal auf, wie „Jetzt ausführen“ im Konto (auch wenn er ausgeschaltet ist; er bleibt dann aus). Das Ergebnis steht nach wenigen Sekunden in list_job_history. Nicht für Heartbeats – die meldet der eigene Server.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "job_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/heartbeats": {
            "post": {
                "operationId": "create_heartbeat",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Heartbeat anlegen",
                "description": "Legt einen Heartbeat an – für einen Job, den NICHT Cronjob.de aufruft, sondern der woanders läuft: Crontab auf dem eigenen Server, Backup-Skript, n8n, Make, Laravel, Supabase, Vercel, Netlify, GitHub Actions. Der Job ruft nach jedem Lauf seine ping_url auf; bleibt diese Meldung aus, warnt Cronjob.de. Läuft das Skript dagegen als Cronjob bei Cronjob.de (Cronjob.de ruft seine URL auf), ist das Endsignal richtig: update_job mit end_signal_minutes. Zeit entweder als Rhythmus (rhythm_minutes, dazu grace_minutes) oder als genauer Zeitplan (schedule als Cron-Ausdruck, dazu grace_minutes) – für \"läuft jede Nacht um 3 Uhr\" passt schedule \"0 3 * * *\". Liefert den Heartbeat mit ping_url, status \"waiting_for_first_ping\" und snippets: fertige Zeilen für linux (Crontab, meldet den Exit-Code mit /$?), php, python und node. Gib dem Nutzer die passende Zeile zum Einbauen; überwacht wird ab der ersten Meldung. alerts nennt, wer gewarnt wird. Hatte das Konto noch keine Warnregel für Heartbeats, wird wie im Assistenten die Kontoadresse eingetragen (alerts.added) – sag dem Nutzer, wohin die Warnungen gehen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Name des Heartbeats (max. 255 Zeichen), z. B. \"Nächtliches Backup\"."
                                    },
                                    "rhythm_minutes": {
                                        "type": "integer",
                                        "description": "Rhythmus in Minuten: so oft meldet sich der Job mindestens (1 bis 20160 = 14 Tage), z. B. 60 = stündlich, 1440 = täglich. Gewarnt wird, wenn nach der letzten Meldung rhythm_minutes plus grace_minutes vergehen, ohne dass eine neue kommt – egal, zu welcher Uhrzeit der Job läuft. Nicht zusammen mit schedule."
                                    },
                                    "grace_minutes": {
                                        "type": "integer",
                                        "description": "Spielraum in Minuten (1 bis 10080 = 7 Tage): so viel später als erwartet darf die Meldung kommen. Ohne Angabe beim Rhythmus 1 bis 15 Minuten je nach Rhythmus, beim Zeitplan 15 Minuten. Für Jobs mit schwankender Laufzeit großzügiger wählen."
                                    },
                                    "schedule": {
                                        "type": "string",
                                        "description": "Genauer Zeitplan als Cron-Ausdruck mit 5 Feldern \"min std tag monat wochentag\" in deutscher Zeit, z. B. \"0 3 * * *\" (täglich 3:00) oder \"30 6 * * 1-5\", auch @daily, @weekly. Sind Tag UND Wochentag eingeschränkt, müssen beide zutreffen (UND). Gewarnt wird, wenn grace_minutes nach einem Termin keine Meldung da ist. Nicht zusammen mit rhythm_minutes; für \"jede Minute\" rhythm_minutes 1 nehmen."
                                    },
                                    "paused": {
                                        "type": "boolean",
                                        "description": "Pausiert anlegen? Standard false. Ein pausierter Heartbeat wird mit seiner nächsten Meldung wieder aktiv."
                                    }
                                },
                                "required": [
                                    "name"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/failures": {
            "get": {
                "operationId": "list_failures",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Fehlgeschlagene Aufrufe suchen",
                "description": "Listet fehlgeschlagene Aufrufe ueber ALLE Cronjobs des Kontos, neueste zuerst. Ein Aufruf gilt als fehlgeschlagen, wenn er eine Fehlermeldung hinterlassen hat; erfolgreiche Aufrufe haben keine. Das Aufrufprotokoll umfasst die letzten 20 Laeufe je Cronjob; wie weit das zurueckreicht, haengt davon ab, wie oft ein Job laeuft. coverage_complete_since nennt den Zeitpunkt, ab dem alle Cronjobs lueckenlos abgedeckt sind - diesen Zeitraum bitte in der Antwort nennen, damit klar ist, worauf sich das Ergebnis bezieht. Das ist eine Eigenschaft des Protokolls und kein Mangel: KEINE Vorschlaege machen, daran etwas zu aendern. notify_after_failures sagt, wann der Kunde benachrichtigt wird: 1 = sofort, 2 bis 10 = erst nach so vielen Fehlschlaegen in Folge, 0 = nie (der Kunde nimmt Fehler dieses Jobs bewusst hin).",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "hours",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "description": "Zeitraum rueckwaerts in Stunden. Standard 24, hoechstens 168 (sieben Tage)."
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "description": "Hoechstzahl der Eintraege. Standard 50, hoechstens 200."
                        }
                    }
                ]
            }
        },
        "/folders": {
            "get": {
                "operationId": "list_folders",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Ordner auflisten",
                "description": "Listet alle Ordner des Nutzers.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            },
            "post": {
                "operationId": "create_folder",
                "tags": [
                    "Cronjobs und Heartbeats"
                ],
                "summary": "Ordner anlegen",
                "description": "Legt einen neuen Ordner an (oder liefert den vorhandenen gleichen Namens).",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Ordnername (max. 100 Zeichen)."
                                    }
                                },
                                "required": [
                                    "name"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/monitors": {
            "get": {
                "operationId": "list_monitors",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachungen auflisten",
                "description": "Listet die Serverüberwachungen (Website-/Server-Monitoring unter „Überwachung“): Cronjob.de ruft jede Seite jede Minute ab und warnt bei einer Störung. Je Eintrag Zustand (state: ok, suspected = erster Fehlschlag, wird bestätigt; checking = Gegenprüfung von einem zweiten Server; down = bestätigte Störung; blocked = Seite sperrt unsere Abrufe), seit wann, Grund, letzte Antwortzeit, Verfügbarkeit der letzten 30 Tage und wer gewarnt wird.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            },
            "post": {
                "operationId": "create_monitor",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachung anlegen",
                "description": "Legt eine Serverüberwachung an (Menü „Überwachung“) – richtig für „überwache meine Website/meinen Server/meine API“: Cronjob.de ruft die Adresse jede Minute ab, bestätigt einen Ausfall von einem zweiten Server und warnt dann. NICHT dafür create_job verwenden: Ein Cronjob ruft eine Adresse auf, um dort etwas auszulösen. Ab Unlimited, höchstens 50 je Konto. Gewarnt werden die Standardkontakte (list_contacts); andere Empfänger mit set_alert_recipients (scope \"monitor\").",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Name, höchstens 120 Zeichen. Ohne Angabe der Domainname."
                                    },
                                    "url": {
                                        "type": "string",
                                        "description": "Adresse der Seite mit http:// oder https://, öffentlich erreichbar (keine internen Netze)."
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "description": "Aktiv? Standard true. Aktiv angelegt wird nur, wenn die Seite die erste Prüfung sofort besteht; sonst kommt der Befund als Fehler zurück (z. B. Firewall sperrt den User-Agent \"Cronjob.de\"). Pausiert anlegen umgeht die Prüfung."
                                    },
                                    "must_contain": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "Texte, die auf der Seite stehen müssen (höchstens 5, je bis 190 Zeichen). Fehlt einer, gilt die Seite als gestört. Leere Liste entfernt alle."
                                    },
                                    "must_not_contain": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "Texte, die NICHT auf der Seite stehen dürfen, z. B. \"Wartungsmodus\" (höchstens 5). Leere Liste entfernt alle."
                                    },
                                    "detect_error_page": {
                                        "type": "boolean",
                                        "description": "Typische Fehlerseiten erkennen (PHP Fatal error, WordPress \"kritischer Fehler\", Datenbankfehler …), auch wenn der Server Status 200 schickt. Standard true."
                                    },
                                    "detect_empty": {
                                        "type": "boolean",
                                        "description": "Leere oder fast leere Antwort als Störung werten. Standard true."
                                    },
                                    "quiet_from": {
                                        "type": "string",
                                        "description": "Ruhezeit Beginn \"HH:MM\" (deutsche Zeit), z. B. \"02:00\". In der Ruhezeit wird gemessen, aber nicht gewarnt; eine Störung, die danach noch besteht, wird gemeldet. Zusammen mit quiet_to; beide \"\" entfernt die Ruhezeit."
                                    },
                                    "quiet_to": {
                                        "type": "string",
                                        "description": "Ruhezeit Ende \"HH:MM\"."
                                    }
                                },
                                "required": [
                                    "url"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/monitors/{monitor_id}": {
            "get": {
                "operationId": "get_monitor",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachung ansehen",
                "description": "Details einer Serverüberwachung: Regeln, Zustand, Verfügbarkeit (24 h, 7 und 30 Tage), Antwortzeiten (Mittel, 95. Perzentil, Maximum der letzten 24 h), Störungen der letzten 90 Tage, versandte Warnungen der letzten 30 Tage und das SSL-Zertifikat der Adresse.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "parameters": [
                    {
                        "name": "monitor_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ]
            },
            "patch": {
                "operationId": "update_monitor",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachung ändern",
                "description": "Ändert eine Serverüberwachung. Nur übergebene Felder werden geändert. Ändern sich Adresse oder Regeln einer aktiven Überwachung, muss die Seite die neue Prüfung sofort bestehen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "Name, höchstens 120 Zeichen. Ohne Angabe der Domainname."
                                    },
                                    "url": {
                                        "type": "string",
                                        "description": "Adresse der Seite mit http:// oder https://, öffentlich erreichbar (keine internen Netze)."
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "description": "Aktiv? Standard true. Aktiv angelegt wird nur, wenn die Seite die erste Prüfung sofort besteht; sonst kommt der Befund als Fehler zurück (z. B. Firewall sperrt den User-Agent \"Cronjob.de\"). Pausiert anlegen umgeht die Prüfung."
                                    },
                                    "must_contain": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "Texte, die auf der Seite stehen müssen (höchstens 5, je bis 190 Zeichen). Fehlt einer, gilt die Seite als gestört. Leere Liste entfernt alle."
                                    },
                                    "must_not_contain": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "description": "Texte, die NICHT auf der Seite stehen dürfen, z. B. \"Wartungsmodus\" (höchstens 5). Leere Liste entfernt alle."
                                    },
                                    "detect_error_page": {
                                        "type": "boolean",
                                        "description": "Typische Fehlerseiten erkennen (PHP Fatal error, WordPress \"kritischer Fehler\", Datenbankfehler …), auch wenn der Server Status 200 schickt. Standard true."
                                    },
                                    "detect_empty": {
                                        "type": "boolean",
                                        "description": "Leere oder fast leere Antwort als Störung werten. Standard true."
                                    },
                                    "quiet_from": {
                                        "type": "string",
                                        "description": "Ruhezeit Beginn \"HH:MM\" (deutsche Zeit), z. B. \"02:00\". In der Ruhezeit wird gemessen, aber nicht gewarnt; eine Störung, die danach noch besteht, wird gemeldet. Zusammen mit quiet_to; beide \"\" entfernt die Ruhezeit."
                                    },
                                    "quiet_to": {
                                        "type": "string",
                                        "description": "Ruhezeit Ende \"HH:MM\"."
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "monitor_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            },
            "delete": {
                "operationId": "delete_monitor",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachung löschen",
                "description": "Löscht eine Serverüberwachung. Zur Sicherheit muss confirm_name exakt dem Namen entsprechen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "confirm_name": {
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "confirm_name"
                                ]
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "monitor_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/monitors/{monitor_id}/active": {
            "put": {
                "operationId": "set_monitor_active",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "Serverüberwachung an- oder abschalten",
                "description": "Pausiert eine Serverüberwachung oder schaltet sie wieder ein (beim Einschalten ohne neue Prüfung).",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "active": {
                                        "type": "boolean"
                                    }
                                },
                                "required": [
                                    "active"
                                ]
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "monitor_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        },
        "/ssl-certificates": {
            "get": {
                "operationId": "list_ssl_certificates",
                "tags": [
                    "Serverüberwachung und SSL"
                ],
                "summary": "SSL-Zertifikate ansehen",
                "description": "SSL-Zertifikate aller https-Adressen des Kontos (aus Cronjobs und Serverüberwachungen): gültig bis, Resttage, Aussteller, Prüffehler. Cronjob.de prüft täglich und warnt vor dem Ablauf (Empfänger: set_alert_recipients mit scope \"ssl\").",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            }
        },
        "/contacts": {
            "get": {
                "operationId": "list_contacts",
                "tags": [
                    "Benachrichtigungen"
                ],
                "summary": "Benachrichtigungen ansehen",
                "description": "Die Kontakte für Warnungen (E-Mail, Slack/Discord/Teams, Telegram, eigener Webhook, SMS) mit ID, ob Standardkontakt und ob bestätigt (fremde E-Mail-Adressen bekommen erst nach ihrem Bestätigungslink Warnungen). Dazu rules: wer wofür gewarnt wird – mode \"standard\" (die Standardkontakte), \"custom\" (contact_ids) oder \"off\" – für alle Cronjobs, einzelne Ordner, alle Heartbeats, einzelne Heartbeats, alle Serverüberwachungen, einzelne Überwachungen und SSL.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            },
            "post": {
                "operationId": "add_contact",
                "tags": [
                    "Benachrichtigungen"
                ],
                "summary": "Kontakt für Warnungen anlegen",
                "description": "Legt einen Kontakt für Warnungen an. type \"email\" (ab Plus), \"chat\" (Webhook-Adresse aus Slack, Discord oder Microsoft Teams, ab Unlimited) oder \"webhook\" (eigener HTTPS-Endpunkt, ab Unlimited; das Signatur-Geheimnis steht im Konto unter Benachrichtigungen › Bearbeiten). Eine fremde E-Mail-Adresse (nicht die des Kontos oder eines Team-Zugangs) bekommt eine Mail mit Bestätigungslink und erst danach Warnungen – sag das dem Nutzer. Telegram lässt sich nur im Konto verbinden. Gibt es den Kontakt schon, kommt der vorhandene zurück.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "email",
                                            "chat",
                                            "webhook"
                                        ]
                                    },
                                    "target": {
                                        "type": "string",
                                        "description": "E-Mail-Adresse bzw. Webhook-Adresse."
                                    },
                                    "name": {
                                        "type": "string",
                                        "description": "Anzeigename, optional (z. B. \"Bereitschaft\")."
                                    },
                                    "standard": {
                                        "type": "boolean",
                                        "description": "Standardkontakt? Standardkontakte bekommen alles, wofür keine eigene Auswahl gilt. Standard false."
                                    },
                                    "cc": {
                                        "type": "boolean",
                                        "description": "Nur E-Mail: in Kopie (CC) statt als Empfänger."
                                    }
                                },
                                "required": [
                                    "type",
                                    "target"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/alert-recipients": {
            "put": {
                "operationId": "set_alert_recipients",
                "tags": [
                    "Benachrichtigungen"
                ],
                "summary": "Empfänger der Warnungen festlegen",
                "description": "Legt fest, wer wofür gewarnt wird. scope: \"all_cronjobs\", \"folder\" (id = Ordner-ID, 0 = unsortiert), \"all_heartbeats\", \"heartbeat\" (id = Job-ID des Heartbeats), \"all_monitors\", \"monitor\" (id = Überwachungs-ID), \"ssl\". mode \"standard\" = die Standardkontakte (bzw. die Einstellung der Gruppe darüber), \"custom\" = genau contact_ids, \"off\" = niemand. Nur bestätigte Kontakte werden tatsächlich benachrichtigt.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "scope": {
                                        "type": "string",
                                        "enum": [
                                            "all_cronjobs",
                                            "folder",
                                            "all_heartbeats",
                                            "heartbeat",
                                            "all_monitors",
                                            "monitor",
                                            "ssl"
                                        ]
                                    },
                                    "id": {
                                        "type": "integer",
                                        "description": "Nur bei folder, heartbeat und monitor."
                                    },
                                    "mode": {
                                        "type": "string",
                                        "enum": [
                                            "standard",
                                            "custom",
                                            "off"
                                        ]
                                    },
                                    "contact_ids": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "description": "Bei mode \"custom\": IDs aus list_contacts."
                                    }
                                },
                                "required": [
                                    "scope",
                                    "mode"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/status-pages": {
            "get": {
                "operationId": "list_status_pages",
                "tags": [
                    "Statusseiten"
                ],
                "summary": "Statusseiten auflisten",
                "description": "Die öffentlichen Statusseiten des Kontos (status.cronjob.de/<slug>/) mit Titel, Adresse, ob online, Sprache und den gezeigten Einträgen (Serverüberwachungen und Cronjobs mit Anzeigename). Nur für Inhaber des Kontos.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                }
            },
            "post": {
                "operationId": "create_status_page",
                "tags": [
                    "Statusseiten"
                ],
                "summary": "Statusseite anlegen",
                "description": "Legt eine öffentliche Statusseite an (status.cronjob.de/<slug>/): zeigt Besuchern die Verfügbarkeit ausgewählter Serverüberwachungen und Cronjobs der letzten 90 Tage. Ab Unlimited, höchstens 10 Seiten, nur für Inhaber. Ohne entries werden alle Serverüberwachungen der Domain aus link übernommen. Ein Logo lässt sich nur im Konto hochladen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "description": "Titel, höchstens 80 Zeichen."
                                    },
                                    "slug": {
                                        "type": "string",
                                        "description": "Adresse status.cronjob.de/<slug>/: 3 bis 40 Zeichen a-z, 0-9 und Bindestrich. Ohne Angabe aus dem Titel."
                                    },
                                    "link": {
                                        "type": "string",
                                        "description": "Link zur eigenen Website (optional, \"\" entfernt)."
                                    },
                                    "language": {
                                        "type": "string",
                                        "enum": [
                                            "de",
                                            "en"
                                        ]
                                    },
                                    "show_incidents": {
                                        "type": "boolean",
                                        "description": "Vorfälle der letzten 30 Tage zeigen. Standard true."
                                    },
                                    "published": {
                                        "type": "boolean",
                                        "description": "Öffentlich schalten (true) oder offline (false). Öffentlich geht nur mit bezahltem Unlimited oder in der Testphase mit Unlimited-Abo; sonst wird der Wunsch gemerkt und die Seite geht nach der Zahlung von selbst online."
                                    },
                                    "entries": {
                                        "type": "array",
                                        "description": "Einträge der Seite in dieser Reihenfolge (höchstens 30). Ersetzt die ganze Liste.",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "type": {
                                                    "type": "string",
                                                    "enum": [
                                                        "monitor",
                                                        "job"
                                                    ],
                                                    "description": "monitor = Serverüberwachung, job = Cronjob oder Heartbeat."
                                                },
                                                "id": {
                                                    "type": "integer"
                                                },
                                                "display_name": {
                                                    "type": "string",
                                                    "description": "Name auf der öffentlichen Seite, höchstens 80 Zeichen, ohne Adressen (z. B. \"Shop\", \"API\")."
                                                }
                                            },
                                            "required": [
                                                "type",
                                                "id",
                                                "display_name"
                                            ]
                                        }
                                    }
                                },
                                "required": [
                                    "title"
                                ]
                            }
                        }
                    }
                },
                "x-requires-plan": "unlimited"
            }
        },
        "/status-pages/{status_page_id}": {
            "patch": {
                "operationId": "update_status_page",
                "tags": [
                    "Statusseiten"
                ],
                "summary": "Statusseite ändern",
                "description": "Ändert eine Statusseite. Nur übergebene Felder werden geändert; entries ersetzt die ganze Liste.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string",
                                        "description": "Titel, höchstens 80 Zeichen."
                                    },
                                    "slug": {
                                        "type": "string",
                                        "description": "Adresse status.cronjob.de/<slug>/: 3 bis 40 Zeichen a-z, 0-9 und Bindestrich. Ohne Angabe aus dem Titel."
                                    },
                                    "link": {
                                        "type": "string",
                                        "description": "Link zur eigenen Website (optional, \"\" entfernt)."
                                    },
                                    "language": {
                                        "type": "string",
                                        "enum": [
                                            "de",
                                            "en"
                                        ]
                                    },
                                    "show_incidents": {
                                        "type": "boolean",
                                        "description": "Vorfälle der letzten 30 Tage zeigen. Standard true."
                                    },
                                    "published": {
                                        "type": "boolean",
                                        "description": "Öffentlich schalten (true) oder offline (false). Öffentlich geht nur mit bezahltem Unlimited oder in der Testphase mit Unlimited-Abo; sonst wird der Wunsch gemerkt und die Seite geht nach der Zahlung von selbst online."
                                    },
                                    "entries": {
                                        "type": "array",
                                        "description": "Einträge der Seite in dieser Reihenfolge (höchstens 30). Ersetzt die ganze Liste.",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "type": {
                                                    "type": "string",
                                                    "enum": [
                                                        "monitor",
                                                        "job"
                                                    ],
                                                    "description": "monitor = Serverüberwachung, job = Cronjob oder Heartbeat."
                                                },
                                                "id": {
                                                    "type": "integer"
                                                },
                                                "display_name": {
                                                    "type": "string",
                                                    "description": "Name auf der öffentlichen Seite, höchstens 80 Zeichen, ohne Adressen (z. B. \"Shop\", \"API\")."
                                                }
                                            },
                                            "required": [
                                                "type",
                                                "id",
                                                "display_name"
                                            ]
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "status_page_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            },
            "delete": {
                "operationId": "delete_status_page",
                "tags": [
                    "Statusseiten"
                ],
                "summary": "Statusseite löschen",
                "description": "Löscht eine Statusseite; sie ist danach nicht mehr erreichbar. confirm_slug muss exakt dem slug entsprechen.",
                "responses": {
                    "200": {
                        "description": "OK (JSON)"
                    },
                    "400": {
                        "description": "Invalid input; {\"error\": \"...\"}"
                    },
                    "401": {
                        "description": "Missing or invalid API key"
                    },
                    "403": {
                        "description": "Plan or key does not allow changes"
                    },
                    "404": {
                        "description": "Not found"
                    },
                    "429": {
                        "description": "Rate limit"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "confirm_slug": {
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "confirm_slug"
                                ]
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "status_page_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "x-requires-plan": "unlimited"
            }
        }
    }
}