{
    "openapi": "3.1.0",
    "info": {
        "title": "SMSForge API",
        "version": "1.37.1",
        "summary": "SMS an deutsche Mobilfunknummern senden, Zustellstatus abfragen, Webhook-Eingang und Heartbeat.",
        "description": "SMSForge verschickt SMS an deutsche Mobilfunknummern per JSON-API oder einfacher GET-URL.\n\nAuthentifizierung: Token im Header `Authorization: Bearer TOKEN` (oder `X-API-Token`). Für die GET-URL ist zusätzlich `?token=` möglich, das Token landet dann aber in Server-Logs.\n\nMit `dry=1` wird alles geprüft (Token, Nummer, Länge, Limit), ohne eine SMS zu senden oder zu zählen.\n\nAusführliche Dokumentation: https://smsforge.de/dokumentation",
        "contact": {
            "name": "SMSForge",
            "url": "https://smsforge.de"
        }
    },
    "externalDocs": {
        "description": "API-Dokumentation",
        "url": "https://smsforge.de/dokumentation"
    },
    "servers": [
        {
            "url": "https://smsforge.de/api"
        }
    ],
    "tags": [
        {
            "name": "SMS",
            "description": "SMS senden und Status abfragen"
        },
        {
            "name": "Monitoring",
            "description": "Webhook-Eingang und Heartbeat (Schlüssel in der URL statt Token)"
        },
        {
            "name": "Status",
            "description": "Zustand des Gateways"
        }
    ],
    "paths": {
        "/sms": {
            "post": {
                "tags": [
                    "SMS"
                ],
                "operationId": "sendSms",
                "summary": "SMS senden (JSON)",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "tokenHeader": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SmsRequest"
                            },
                            "example": {
                                "to": [
                                    "+491701234567"
                                ],
                                "text": "Backup fertig"
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Angenommen, die SMS ist in der Warteschlange.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Accepted"
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "Nur bei `dry`: alles geprüft, nichts gesendet.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DryRun"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ungültige Nummer, Text leer oder zu lang, zu viele Empfänger, Sperrliste oder Tageslimit erreicht.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Token fehlt oder ist ungültig bzw. widerrufen.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Konto gesperrt, E-Mail oder Handynummer nicht bestätigt oder IP für das Token nicht freigegeben.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen (Rate-Limit) oder Token durch die Notbremse pausiert.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Wartungsarbeiten: die API ist vorübergehend nicht verfügbar.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/send": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "sendSmsGet",
                "summary": "SMS senden (GET-URL für einfache Systeme)",
                "description": "Für Router, NAS und Skripte, die nur eine URL aufrufen können. Text URL-kodieren.",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "tokenHeader": []
                    },
                    {
                        "tokenQuery": []
                    }
                ],
                "parameters": [
                    {
                        "name": "to",
                        "in": "query",
                        "required": true,
                        "description": "Empfänger, mehrere durch Komma getrennt.",
                        "schema": {
                            "type": "string",
                            "example": "+491701234567"
                        }
                    },
                    {
                        "name": "text",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "example": "Backup fertig"
                        }
                    },
                    {
                        "name": "dry",
                        "in": "query",
                        "required": false,
                        "description": "Nur prüfen, nicht senden.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "format",
                        "in": "query",
                        "required": false,
                        "description": "`text` liefert die Antwort als Klartext (`OK 4711`).",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "json",
                                "text"
                            ]
                        }
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Angenommen.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Accepted"
                                }
                            },
                            "text/plain": {
                                "schema": {
                                    "type": "string",
                                    "example": "OK 4711"
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "Nur bei `dry`.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DryRun"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Eingabe oder Limit.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Token fehlt oder ist ungültig bzw. widerrufen.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Konto gesperrt, E-Mail oder Handynummer nicht bestätigt oder IP für das Token nicht freigegeben.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen (Rate-Limit) oder Token durch die Notbremse pausiert.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Wartungsarbeiten: die API ist vorübergehend nicht verfügbar.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/sms/{id}": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "getSms",
                "summary": "Zustellstatus einer SMS",
                "security": [
                    {
                        "bearerAuth": []
                    },
                    {
                        "tokenHeader": []
                    }
                ],
                "description": "Es werden nur eigene SMS angezeigt. Gesendete SMS werden nach 30 Tagen gelöscht.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Die Nachricht.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Unbekannte ID oder nicht deine Nachricht.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Token fehlt oder ist ungültig bzw. widerrufen.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Konto gesperrt, E-Mail oder Handynummer nicht bestätigt oder IP für das Token nicht freigegeben.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Zu viele Anfragen (Rate-Limit) oder Token durch die Notbremse pausiert.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Wartungsarbeiten: die API ist vorübergehend nicht verfügbar.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/hook/{key}": {
            "post": {
                "tags": [
                    "Monitoring"
                ],
                "operationId": "webhookIn",
                "summary": "Webhook-Eingang (Grafana, Alertmanager, Uptime Kuma, eigene Dienste)",
                "description": "Der geheime Schlüssel in der URL ersetzt das Token. SMSForge erkennt das Format, kürzt auf eine SMS und fasst gleiche Meldungen innerhalb von 10 Minuten zusammen. Auch per GET (`?text=`) möglich.",
                "security": [],
                "parameters": [
                    {
                        "name": "key",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": true
                            }
                        },
                        "text/plain": {
                            "schema": {
                                "type": "string"
                            }
                        },
                        "application/x-www-form-urlencoded": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": true
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "SMS angelegt.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "sms": {
                                            "type": "integer"
                                        },
                                        "text": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Unbekannter Schlüssel."
                    },
                    "409": {
                        "description": "Eingang ist pausiert.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "413": {
                        "description": "Aufruf größer als 64 KB.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Aus dem Aufruf ließ sich kein Text bilden.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Stundengrenze des Eingangs erreicht.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/ping/{key}": {
            "get": {
                "tags": [
                    "Monitoring"
                ],
                "operationId": "heartbeatPing",
                "summary": "Heartbeat-Ping (Lebenszeichen eines Jobs)",
                "description": "Der Job ruft die URL nach getaner Arbeit auf. Bleibt der Ping aus, sendet SMSForge eine SMS. `/ping/{key}/fail` meldet sofort einen Fehler.",
                "security": [],
                "parameters": [
                    {
                        "name": "key",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`OK`, oder `PAUSED` wenn der Heartbeat pausiert ist.",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "type": "string",
                                    "enum": [
                                        "OK",
                                        "PAUSED"
                                    ]
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Unbekannter Schlüssel."
                    }
                }
            }
        },
        "/health": {
            "get": {
                "tags": [
                    "Status"
                ],
                "operationId": "health",
                "summary": "Zustand des Gateways (für externes Monitoring)",
                "description": "Ohne Anmeldung abrufbar. HTTP 200, wenn das Gateway versandbereit ist (oder gewartet wird), sonst 503. Geeignet für Uptime Kuma, UptimeRobot und ähnliche Dienste.",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Versandbereit oder Wartung.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Health"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Eingeschränkt oder Störung.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Health"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "API-Token aus „API & Konto“."
            },
            "tokenHeader": {
                "type": "apiKey",
                "in": "header",
                "name": "X-API-Token"
            },
            "tokenQuery": {
                "type": "apiKey",
                "in": "query",
                "name": "token",
                "description": "Nur für die GET-URL. Das Token landet in Server-Logs, besser den Header nutzen."
            }
        },
        "schemas": {
            "SmsRequest": {
                "type": "object",
                "required": [
                    "to",
                    "text"
                ],
                "properties": {
                    "to": {
                        "description": "Eine Nummer oder eine Liste (E.164 oder 0170…).",
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        ]
                    },
                    "text": {
                        "type": "string",
                        "description": "Höchstens 480 Zeichen; ohne bestätigte Handynummer eine SMS, mit bestätigter Nummer bis zu 3 SMS."
                    },
                    "dry": {
                        "type": "boolean",
                        "description": "Nur prüfen, nichts senden."
                    }
                }
            },
            "Accepted": {
                "type": "object",
                "properties": {
                    "messages": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "type": "integer"
                                },
                                "to": {
                                    "type": "string"
                                },
                                "status": {
                                    "type": "string",
                                    "example": "queued"
                                }
                            }
                        }
                    }
                }
            },
            "DryRun": {
                "type": "object",
                "properties": {
                    "dry_run": {
                        "type": "boolean"
                    },
                    "ok": {
                        "type": "boolean"
                    },
                    "recipients": {
                        "type": "array",
                        "items": {
                            "type": "object"
                        }
                    },
                    "skipped": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "remaining_today": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    }
                }
            },
            "Message": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "direction": {
                        "type": "string",
                        "enum": [
                            "out",
                            "in"
                        ]
                    },
                    "number": {
                        "type": "string"
                    },
                    "text": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "queued",
                            "sending",
                            "sent",
                            "delivered",
                            "failed"
                        ],
                        "description": "queued: wartet. sending: wird gesendet. sent: ans Netz übergeben. delivered: zugestellt (Zustellbericht). failed: fehlgeschlagen, Grund in `error`."
                    },
                    "error": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "error_hint": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Lesbare Erklärung bei failed."
                    },
                    "sent_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "received_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "Health": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": [
                            "ok",
                            "degraded",
                            "down",
                            "maintenance"
                        ]
                    },
                    "gateway": {
                        "type": "string",
                        "enum": [
                            "ready",
                            "no_network",
                            "offline"
                        ]
                    },
                    "queue": {
                        "type": "integer",
                        "description": "SMS in der Warteschlange."
                    },
                    "oldest_queued_seconds": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Alter der ältesten wartenden SMS."
                    },
                    "checked_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                }
            },
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "string"
                    },
                    "message": {
                        "type": "string"
                    }
                }
            }
        }
    }
}