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
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%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.| Field | Type | Notes |
|---|---|---|
| to required | string | string[] | Comma-separated string or array. +977/977 prefixes and spaces/dashes are normalized to the local form. |
| message required | string | The content. It replaces {{message}} in the account's template. If the account has no template, it's sent as-is. |
| account optional | string | Preferred sending account (username). Soft preference — falls back to rotation if it's exhausted. |
| async optional | boolean | true always answers 202 as soon as the message is queued, without waiting for the panel. Best for bulk senders. |
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.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"}'
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. …"
}| Status | Meaning |
|---|---|
| 200 | Sent (balance decreased). success:true. |
| 202 | Queued — accepted and will be sent within etaSec. Poll GET /api/message/:id for the result. Never retry a 202. |
| 422 | dropped:true — the account has templates but none contain {{message}}, so nothing was sent. |
| 400 | Missing to/message or no valid numbers. |
| 429 | The queue itself is full (default 5000 waiting). Honour Retry-After and resend. |
| 502 | Send could not be confirmed (balance didn't change / error flash). |
| 503 | All accounts exhausted, or the browser couldn't complete the flow. |
id from POST /api/send — the
delivery-report equivalent. status is queued → sending →
sent | 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
}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.{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", "…": "…" } ]
}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 worker —
maxPerMinuteTotal 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 }
}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": [] }curl -X POST %BASE%/api/reset -H "x-api-key: %KEY%"{"ok":true}. No auth required.curl %BASE%/healthEach 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:
{{message}} → the message is dropped (HTTP 422); nothing is sent.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.
All require x-api-key. Mutating settings/accounts apply immediately and persist to MongoDB.
{username, password, senderNumber?, templates?}. Resets rotation to the top and drops the live session.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.{paused:true|false}. Holds the send queue (the in-flight message still finishes); accepted messages wait until you resume.{username} (or all if empty). Runs in a separate browser session; also resets that account's sent counter.{username} for one, empty body for all.{username} (or all if empty) so it re-enters rotation.Acts on the smssharda panel under a panel admin login you supply. All require x-api-key;
fetched users are cached in MongoDB.
{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.{q?, adminUsername?, status?, limit?, skip?}.{userIds:number[]}. Adds the users to the sending rotation using their Password Viewer value + first Sender ID; existing accounts get credentials refreshed (no duplicates).{adminUsername, adminPassword, userIds}. Activates inactive users (already-active are skipped; only the Status field is touched).{adminUsername, adminPassword, userIds, amount}. Adds amount credits to each user.{adminUsername, adminPassword, userIds}. Permanently deletes the users on the panel.POST /api/users/admins/forget {username} removes one.