louislam/uptime-kuma · error · Error
SMSEagle API returned an empty response
Error message
SMSEagle API returned an empty response
What it means
Thrown by the SMSEagle apiv2 path when the HTTP response is non-200 OR no recipients landed in 'queued' status, AND resp.data is an empty array (length 0). The v2 /api/v2 endpoints return an array of per-recipient status objects; an empty array means the gateway accepted the request but produced zero deliverable status entries, so there is nothing to report.
Source
Thrown at server/notification-providers/smseagle.js:127
if (notification.smseagleMsgType === "smseagle-ring") {
endpoint = "/calls/ring";
} else if (notification.smseagleMsgType === "smseagle-tts") {
endpoint = "/calls/tts";
} else if (notification.smseagleMsgType === "smseagle-tts-advanced") {
endpoint = "/calls/tts_advanced";
postData["voice_id"] = notification.smseagleTtsModel ?? 1;
}
}
let resp = await axios.post(notification.smseagleUrl + "/api/v2" + endpoint, postData, config);
const queuedCount = resp.data.filter((x) => x.status === "queued").length;
const unqueuedCount = resp.data.length - queuedCount;
if (resp.status !== 200 || queuedCount === 0) {
if (!resp.data.length) {
throw new Error("SMSEagle API returned an empty response");
}
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
}
if (unqueuedCount) {
return `Sent ${queuedCount}/${resp.data.length} Messages Successfully.`;
}
return okMsg;
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = SMSEagle;
View on GitHub (pinned to 6b5ea01557)
Solutions
- In the notification config, ensure at least one of smseagleRecipientTo, smseagleRecipientContact, or smseagleRecipientGroup is populated with valid values.
- Confirm the device firmware supports apiv2 and the {smseagleUrl}/api/v2 base path resolves (visit {smseagleUrl}/api/v2/ping).
- Check that numeric recipient IDs (contacts/groups) are valid integers — non-numeric strings become NaN after .map(Number) and are silently dropped.
- Reproduce the POST with curl using the access-token header to inspect the raw array the device returns.
Example fix
// before
if (resp.status !== 200 || queuedCount === 0) {
if (!resp.data.length) {
throw new Error("SMSEagle API returned an empty response");
}
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
}
// after (surface which recipient source was missing for faster diagnosis)
const hasRecipient = postData.to?.length || postData.contacts?.length || postData.groups?.length;
if (resp.status !== 200 || queuedCount === 0) {
if (!resp.data.length) {
throw new Error(`SMSEagle API returned an empty response (recipients sent: ${!!hasRecipient})`);
}
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
} Defensive patterns
Strategy: validation
Validate before calling
// Ensure at least one recipient source is set before calling the v2 endpoint
const hasRecipient =
(notification.smseagleRecipientTo && notification.smseagleRecipientTo.trim()) ||
(notification.smseagleRecipientContact && notification.smseagleRecipientContact.trim()) ||
(notification.smseagleRecipientGroup && notification.smseagleRecipientGroup.trim());
if (!hasRecipient) {
throw new Error("At least one SMSEagle recipient (to/contact/group) is required for apiv2");
} Type guard
/** True when the v2 response is a non-empty array of status objects. */
function isSmseagleV2StatusArray(data) {
return Array.isArray(data) && data.length > 0 && data.every((x) => x && typeof x.status === "string");
} Try / catch
try {
const resp = await axios.post(url, postData, config);
if (!Array.isArray(resp.data)) {
throw new Error(`SMSEagle v2 returned non-array body: ${JSON.stringify(resp.data)}`);
}
if (resp.status !== 200 || resp.data.filter((x) => x.status === "queued").length === 0) {
if (resp.data.length === 0) throw new Error("SMSEagle API returned an empty response");
throw new Error(`SMSEagle API returned error: ${JSON.stringify(resp.data)}`);
}
} catch (err) {
this.throwGeneralAxiosError(err);
} Prevention
- Always set at least one recipient field when apiv2 is selected.
- Use integer IDs for contacts/groups so .map(Number) does not yield NaN.
- Smoke-test apiv2 with one recipient before going wide.
When it happens
Trigger: POST to {smseagleUrl}/api/v2/messages/sms or /api/v2/calls/* with an empty to/contacts/groups payload, or the device returning 200 with [] because all recipients were filtered out server-side. Also reachable when the device returns a non-2xx status with no body.
Common situations: smseagleRecipientTo/Contact/Group fields left blank while apiv2 is selected, comma-separated IDs that all fail to parse via .map(Number) producing NaN entries the device rejects, or a firmware bug returning [] on internal failure.
Related errors
- SMSEagle API returned error: ${resp.data}
- SMSEagle API returned error: ${JSON.stringify(resp.data)}
- yzj's server did not respond with the expected result
- Additional Headers is not a valid JSON
- Splunk notification failed with status code ${result.status}
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/ab6193da3e748d0a.
Report an issue: GitHub.