louislam/uptime-kuma · error · Error

SMSEagle API returned error: ${resp.data}

Error message

SMSEagle API returned error: ${resp.data}

What it means

Thrown by the SMSEagle provider when the legacy HTTP API (apiv1) replies with a body that does not contain the literal substring 'OK'. The v1 SMS Eagle gateway returns a free-form text body where 'OK' (optionally followed by an ID) marks success; anything else is treated as an error and echoed verbatim into the message. Because the check uses String.indexOf on resp.data, it also implicitly assumes resp.data is a string.

Source

Thrown at server/notification-providers/smseagle.js:72

                url.searchParams.append(recipientType, notification.smseagleRecipient);
                if (!notification.smseagleRecipientType || notification.smseagleRecipientType === "smseagle-sms") {
                    url.searchParams.append("unicode", notification.smseagleEncoding ? "1" : "0");
                    url.searchParams.append("highpriority", notification.smseaglePriority ?? "0");
                } else {
                    url.searchParams.append("duration", duration);
                }
                if (notification.smseagleRecipientType !== "smseagle-ring") {
                    url.searchParams.append("message", msg);
                }
                if (voiceId) {
                    url.searchParams.append("voice_id", voiceId);
                }

                let resp = await axios.get(url.toString(), config);

                if (resp.data.indexOf("OK") === -1) {
                    let error = `SMSEagle API returned error: ${resp.data}`;
                    throw new Error(error);
                }

                return okMsg;
            } else if (notification.smseagleApiType === "smseagle-apiv2") {
                // according to https://www.smseagle.eu/docs/apiv2/
                let config = {
                    headers: {
                        "access-token": notification.smseagleToken,
                        "Content-Type": "application/json",
                    },
                };
                config = this.getAxiosConfigWithProxy(config);

                let encoding = notification.smseagleEncoding ? "unicode" : "standard";
                let priority = notification.smseaglePriority ?? 0;

                let postData = {
                    text: msg,

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Open the SMSEagle web UI and verify the access_token under Setup > User Access Tokens is still active and matches notification.smseagleToken exactly.
  2. Confirm notification.smseagleUrl is the device base URL (no /api/v2 suffix) since v1 uses /http_api.
  3. Verify the recipient value matches an existing contact/group/phone in the device address book.
  4. Test the same URL+token with curl from the Uptime Kuma host to see the raw body returned and rule out network/proxy interference.
  5. If the device firmware now returns JSON, switch the provider config to smseagle-apiv2 (smseagle.js:76) which parses structured responses.

Example fix

// before (v1, fragile string check on possibly non-string body)
let resp = await axios.get(url.toString(), config);
if (resp.data.indexOf("OK") === -1) {
    throw new Error(`SMSEagle API returned error: ${resp.data}`);
}
// after (guard the body type so a JSON/HTML body surfaces a clear message)
const body = typeof resp.data === "string" ? resp.data : JSON.stringify(resp.data);
if (!body.includes("OK")) {
    throw new Error(`SMSEagle API returned error: ${body}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate token + url shape before the request
if (!notification.smseagleToken || !notification.smseagleUrl) {
    throw new Error("SMSEagle token and URL are required");
}
try { new URL(notification.smseagleUrl); } catch { throw new Error("smseagleUrl is not a valid URL"); }

Type guard

/** True when the v1 response body is a success string. */
function isSmseagleV1Ok(data) {
    return typeof data === "string" && data.includes("OK");
}

Try / catch

let resp;
try {
    resp = await axios.get(url.toString(), config);
} catch (err) {
    throw new Error(`SMSEagle request failed: ${err.message}`);
}
const body = typeof resp.data === "string" ? resp.data : JSON.stringify(resp.data);
if (!body.includes("OK")) {
    throw new Error(`SMSEagle API returned error: ${body}`);
}

Prevention

When it happens

Trigger: A GET to {smseagleUrl}/http_api/{sendMethod} with an invalid/expired access_token, an unknown recipient (contact/group/phone), an unreachable modem, or a misconfigured base URL that returns an HTML error page. Also triggered if the device firmware returns JSON instead of plain text, since indexOf('OK') on a non-string would throw before this line.

Common situations: Token copied with trailing whitespace, SMSEagle hostname/IP changed after migration, device in offline/airplane mode, recipient contact name typo, or pointing smseagleUrl at the v2 JSON endpoint by mistake.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/6c062f40b73e8dd8. Report an issue: GitHub.