SMS Gateway — API

← Dashboard Logs Users

HTTP API around the smssharda panel. Send an SMS and the gateway logs in with a funded account, wraps your content in that account's template, submits the form, and returns the result.

AuthPOST /api/sendMessage statusGET /api/queueGET /api/status POST /api/resetGET /health TemplatesAdmin APIUser API

Authentication

Every endpoint except GET /health and the admin HTML pages requires your API key, sent as the x-api-key header (or ?api_key= query param). Missing/incorrect key → 401.

x-api-key: %KEY%

Sending API

POST/api/sendx-api-key
Sends an SMS to one or more numbers. message is your content; the gateway wraps it in the chosen account's template. The account is chosen by rotation (balance / login-health / count based); pass account to prefer a specific one.
FieldTypeNotes
to requiredstring | string[]Comma-separated string or array. +977/977 prefixes and spaces/dashes are normalized to the local form.
message requiredstringThe content. It replaces {{message}} in the account's template. If the account has no template, it's sent as-is.
account optionalstringPreferred sending account (username). Soft preference — falls back to rotation if it's exhausted.
async optionalbooleantrue always answers 202 as soon as the message is queued, without waiting for the panel. Best for bulk senders.
Every accepted request returns an id — on the 200 form too — which is the handle for per-message status. A 202 is a success: never retry it, or the message goes out twice.
Pacing. The panel rate-limits bursts (“Too many SMS requests. Please wait one minute.”) and counts that limit per account, so the gateway holds every send in an internal queue and feeds it to a pool of workers — each signed in as a different account, each paced on its own clock. By default 3 workers at one send every 4 s and max 10 per minute each, so 30 a minute in total (all tunable on the Dashboard). You can therefore fire as many requests as you like: nothing is dropped, and that rate-limit message is never returned to you. If the panel does refuse a submission, only the worker that was refused holds and slows down — the others keep sending — and that same message is submitted again, safe because a refused submission sends nothing.
curlfetch (JS)
curl -X POST %BASE%/api/send \
  -H "x-api-key: %KEY%" \
  -H "Content-Type: application/json" \
  -d '{"to":"9841XXXXXX,9842XXXXXX","message":"Your OTP is 4321"}'
const res = await fetch("%BASE%/api/send", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-api-key": "%KEY%" },
  body: JSON.stringify({ to: ["9841XXXXXX"], message: "Your OTP is 4321" }),
});
const data = await res.json();   // 200 sent · 202 queued (poll data.id) · 422 dropped · 502/503 error

200 success — sent & charged:

{
  "id": "mfa1k2z-57",
  "status": "sent",
  "confirmedBy": "balance-charged",
  "success": true,
  "account": "jayambe",
  "recipients": 2,
  "balanceBefore": 51606.4,
  "balanceAfter": 51603.4,
  "creditsUsed": 3,
  "smsSentThisRotation": 42,
  "rotated": false,
  "wrapped": true,
  "gatewayMessage": "Sent to 2 recipient(s); charged 3.00 credits"
}

202 queued — it has to wait its turn (a burst, or a rate-limit cooldown). The send happens on its own; watch the outcome in the message log:

{
  "accepted": true,
  "queued": true,
  "status": "queued",
  "id": "mfa1k2z-58",
  "position": 12,
  "waiting": 11,
  "etaSec": 44,
  "recipients": 1,
  "note": "Accepted and queued — the gateway paces sends so the panel never rate-limits them. …"
}
StatusMeaning
200Sent (balance decreased). success:true.
202Queued — accepted and will be sent within etaSec. Poll GET /api/message/:id for the result. Never retry a 202.
422dropped:true — the account has templates but none contain {{message}}, so nothing was sent.
400Missing to/message or no valid numbers.
429The queue itself is full (default 5000 waiting). Honour Retry-After and resend.
502Send could not be confirmed (balance didn't change / error flash).
503All accounts exhausted, or the browser couldn't complete the flow.
GET/api/message/:idx-api-key
Status of one message, by the id from POST /api/send — the delivery-report equivalent. status is queuedsendingsent | failed | dropped | error | cancelled. While queued it also carries position, etaSec and, during a rate-limit hold, retryAfter. Unknown id → 404.
curl %BASE%/api/message/mfa1k2z-58 -H "x-api-key: %KEY%"
{
  "id": "mfa1k2z-58", "status": "sent", "confirmedBy": "balance-charged",
  "account": "jayambe", "to": ["9841111111"], "recipients": 1,
  "finalText": "Seven Hills: Your OTP is 4321",
  "acceptedAt": "…", "startedAt": "…", "settledAt": "…",
  "queueWaitMs": 46786, "sendMs": 4240, "durationMs": 51026,
  "attempts": 2, "rateLimited": 1,
  "creditsUsed": 1.2, "balanceBefore": 51606.4, "balanceAfter": 51605.2,
  "gatewayMessage": "Message Sent Successfully", "error": null
}
What “sent” means: the panel accepted the message and charged the account — confirmedBy says whether that came from the balance dropping (balance-charged) or the panel's own success flash (panel-flash). The panel exposes no carrier delivery receipt, so there is no delivered status and handset-level delivery cannot be reported here.
POST/api/messages/statusx-api-key
The same lookup for up to 500 ids at once — how a bulk sender reconciles a batch. Body {ids: string[]}.
curl -X POST %BASE%/api/messages/status   -H "x-api-key: %KEY%" -H "Content-Type: application/json"   -d '{"ids":["mfa1k2z-58","mfa1k2z-59"]}'
{
  "total": 2,
  "counts": { "sent": 1, "queued": 1 },
  "unknown": [],
  "messages": [ { "id": "mfa1k2z-58", "status": "sent", "…": "…" } ]
}
GET/api/queuex-api-key
How much is waiting and how fast it is going out — memory only, so it is cheap to poll. Useful for backpressure: hold off submitting while waiting is high. concurrency is how many workers are sending in parallel right now (min(sendWorkers, accounts above minBalance)), and the pace fields are per workermaxPerMinuteTotal is the gateway-wide figure. cooldownMs is only non-zero while every worker is holding one out, i.e. a genuine stall.
{
  "waiting": 11, "paused": false, "cooldownMs": 0, "nextSendInMs": 2400,
  "concurrency": 3, "intervalMs": 4000, "maxPerMinute": 10, "maxPerMinuteTotal": 30,
  "throttled": false, "sentLastMinute": 27, "drainEtaMs": 34000,
  "active": [ { "id": "q46", "worker": 0, "recipients": 1, "attempts": 1, "rateLimitHits": 0 } ],
  "activeCount": 1,
  "workers": [
    { "id": 0, "busy": true,  "sentLastMinute": 9, "intervalMs": 4000, "cooldownMs": 0, "throttled": false },
    { "id": 1, "busy": false, "sentLastMinute": 9, "intervalMs": 6000, "cooldownMs": 41000, "throttled": true }
  ],
  "totals": { "accepted": 57, "sent": 45, "failed": 1, "rateLimited": 2, "retries": 2 }
}
GET/api/statusx-api-key
Brings the first worker up and reports its account and live balance, plus every slot in the send pool, the current concurrency, and the exhausted list. Note: this drives the headless browser, so the first call can take 20–60 s.
curl %BASE%/api/status -H "x-api-key: %KEY%"
{ "activeAccount": "jayambe", "balance": 51606.4, "poolSize": 3, "concurrency": 3,
  "workers": [ { "id": 0, "account": "jayambe", "idleMs": 1200 },
               { "id": 1, "account": "acca2", "idleMs": 800 },
               { "id": 2, "account": null, "idleMs": null } ],
  "totalAccounts": 8, "exhaustedAccounts": [] }
POST/api/resetx-api-key
Clears the exhausted flags and login-failure counts (use after topping accounts up).
curl -X POST %BASE%/api/reset -H "x-api-key: %KEY%"
GET/healthno key
Liveness probe. Returns {"ok":true}. No auth required.
curl %BASE%/health

Message templates

Each sending account can hold one or more templates with variables {{message}} (your API content) and {{company}}. {{company}} resolves to a manual per-account override if set, otherwise the account's Company Name from the Users data. On send, the gateway substitutes both:

Example: template {{company}}: {{message}} on an account whose company is Seven Hills + content Your OTP is 4321 → sends Seven Hills: Your OTP is 4321. Manage templates and the company value per account on the Dashboard (Accounts → Templates). You normally don't set company at all — it's taken from the matching user's Company Name automatically.

Admin API (used by the dashboard)

All require x-api-key. Mutating settings/accounts apply immediately and persist to MongoDB.

GET/api/admin/overviewx-api-key
Snapshot for the dashboard: accounts (with balance, sent counter, templates), active account, config, storage mode. Never touches the browser.
PUT/api/admin/accountsx-api-key
Replace the whole account list (add/edit/delete/reorder). Body: array of {username, password, senderNumber?, templates?}. Resets rotation to the top and drops the live session.
PUT/api/admin/configx-api-key
Update settings: minBalance, rotateEverySms, maxLoginFailures, navigationTimeoutMs, challengeTimeoutMs, keepAliveSec, headless, apiKey, baseUrl, loginPath, sendSmsPath, mongoUrl, mongoDb, port plus the send pool and pacing — sendWorkers (1–16, accounts sending in parallel) and the per-worker sendIntervalMs, maxSendsPerMinute, rateLimitCooldownMs, maxRateLimitRetries, inlineWaitMs, maxQueueLength. Port/Mongo changes need a restart; pacing applies to the next send.
POST/api/admin/queue/pausex-api-key
Body {paused:true|false}. Holds the send queue (the in-flight message still finishes); accepted messages wait until you resume.
POST/api/admin/queue/clearx-api-key
Cancels every message still waiting — they are logged as errors and never sent. The in-flight one is left alone.
POST/api/admin/balance/checkx-api-key
Start a background balance check for {username} (or all if empty). Runs in a separate browser session; also resets that account's sent counter.
POST/api/admin/counters/resetx-api-key
Reset per-account SMS sent counters: {username} for one, empty body for all.
POST/api/admin/exhausted/clearx-api-key
Un-exhaust {username} (or all if empty) so it re-enters rotation.
POST/api/admin/browser/restartx-api-key
Close the headless browser; it relaunches on the next send/status.

User Management API (used by the Users page)

Acts on the smssharda panel under a panel admin login you supply. All require x-api-key; fetched users are cached in MongoDB.

POST/api/users/fetchx-api-key
Body {adminUsername, adminPassword}. Logs in, scrapes all pages of the Users grid into Mongo (detects new/changed, syncs changed passwords into matching sending accounts), and remembers the admin login.
POST/api/users/browsex-api-key
Search cached users. Body {q?, adminUsername?, status?, limit?, skip?}.
POST/api/users/add-to-accountsx-api-key
Body {userIds:number[]}. Adds the users to the sending rotation using their Password Viewer value + first Sender ID; existing accounts get credentials refreshed (no duplicates).
POST/api/users/activatex-api-key
Body {adminUsername, adminPassword, userIds}. Activates inactive users (already-active are skipped; only the Status field is touched).
POST/api/users/add-creditx-api-key
Body {adminUsername, adminPassword, userIds, amount}. Adds amount credits to each user.
POST/api/users/deletex-api-key
Body {adminUsername, adminPassword, userIds}. Permanently deletes the users on the panel.
GET/api/users/adminsx-api-key
Saved panel-admin logins (for the Users page autofill). POST /api/users/admins/forget {username} removes one.