API Docs

Send SMS from any app with a Bearer API key.

Manage keys

Authentication

Create a key in the portal, then send Authorization: Bearer eg_…

Send SMS

curl -X POST http://localhost:1084/api/v1/messages \
  -H "Authorization: Bearer eg_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"+15551234567","body":"Hello from Eligapris"}'

List messages

curl http://localhost:1084/api/v1/messages \
  -H "Authorization: Bearer eg_YOUR_KEY"

List devices

curl http://localhost:1084/api/v1/devices \
  -H "Authorization: Bearer eg_YOUR_KEY"

Remote command

curl -X POST http://localhost:1084/api/v1/devices/DEVICE_ID/commands \
  -H "Authorization: Bearer eg_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"ring"}'

USSD

curl ... -d '{"type":"send_ussd","payload":{"code":"*100#","subscriptionId":1}}'

Requires ussd grant + CALL_PHONE on the phone. Single-shot only (Android 8+).

Inbox sync

curl ... -d '{"type":"sync_inbox","payload":{"limit":200}}'

Pulls SMS inbox into the portal (idempotent). Live inbound also posts sms.received webhooks when configured under API Keys.

Jobs (allowlisted)

curl ... -d '{"type":"run_job","payload":{"jobId":"battery"}}'
curl ... -d '{"type":"run_job","payload":{"jobId":"ping_once","args":["1.1.1.1"]}}'
curl ... -d '{"type":"run_job","payload":{"jobId":"dns","args":["example.com"]}}'
curl ... -d '{"type":"run_job","payload":{"jobId":"http_head","args":["https://example.com"]}}'

jobId: uname | battery | mem | df | wifi | ip_route | sensors | display | locale | ping_once | dns | http_head. In-process on the phone (no shell). ping_once is a TCP probe to 443/80. Requires jobs grant.

Files (SAF)

curl ... -d '{"type":"list_dir","payload":{"path":"/"}}'
curl ... -d '{"type":"mkdir","payload":{"path":"docs"}}'
curl ... -d '{"type":"delete_file","payload":{"path":"docs/old.txt"}}'
curl ... -d '{"type":"pull_file","payload":{"path":"notes.txt"}}'
curl ... -d '{"type":"push_file","payload":{"path":"out.bin","mediaId":"MEDIA_ID"}}'

Portal Remote: choose a device, then Files (and Capture / Locate / Phone). Pick a folder on the phone (Permissions). Requires files grant.

Locate / volume / power tools

# Find Hub–style locate (requires location grant on phone)
curl ... -d '{"type":"get_location"}'

# Max ring/notification/alarm volume
curl ... -d '{"type":"set_volume","payload":{"level":100,"stream":"ring"}}'

curl ... -d '{"type":"flashlight","payload":{"on":true}}'
curl ... -d '{"type":"set_clipboard","payload":{"text":"paste me"}}'
curl ... -d '{"type":"open_url","payload":{"url":"https://example.com"}}'
curl ... -d '{"type":"call_back","payload":{"to":"+15551234567"}}'

# Multi-SIM SMS (optional subscriptionId)
curl -X POST http://localhost:1084/api/v1/messages \
  -H "Authorization: Bearer eg_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"+15551234567","body":"Hi","subscriptionId":1}'

Grants: location for get_location; remote_commands for volume / flashlight / clipboard / open_url / call_back. Flashlight needs CAMERA OS permission; call_back prefers CALL_PHONE (falls back to dialer).

Photo (front / back)

curl -X POST http://localhost:1084/api/v1/devices/DEVICE_ID/commands \
  -H "Authorization: Bearer eg_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"take_photo","payload":{"camera":"front"}}'

Video

curl -X POST http://localhost:1084/api/v1/devices/DEVICE_ID/commands \
  -H "Authorization: Bearer eg_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"start_video","payload":{"camera":"back","maxSeconds":300,"chunkSeconds":60,"withAudio":true}}'

# later:
curl ... -d '{"type":"stop_video"}'

Audio (Opus / Ogg)

curl ... -d '{"type":"start_audio","payload":{"maxSeconds":300,"chunkSeconds":60}}'
curl ... -d '{"type":"stop_audio"}'

Sessions default to 5 minutes and upload in time chunks (default 60s) instead of one hard cut. Caps: maxSeconds 60–900, chunkSeconds 30–180.

Android pairing

  1. Portal → Devices → Generate code
  2. Android app → Pair → enter 6-digit code (confirm the prefilled SIM number for SMS wake)
  3. Enable SMS / USSD / camera / mic / remote / location / jobs / files grants on the phone
  4. Gateway starts after pair so the portal can show online

Phone subprocess (:hub)

The Android app is one APK with two processes — not a cloud microservice:

  • com.eligapris.smsgateway — UI (Compose). Pairing, grants, settings. Optional after pair.
  • com.eligapris.smsgateway:hub — headless worker. Owns the foreground service, SSE/poll, SMS receivers, boot restart, and command execution.

OS runtime permissions (camera, SMS, …) are package-wide and shared by both processes. Capability grants (Remote toggles) are mirrored from the UI into an atomic bridge file so the hub enforces the same policy when the UI is closed. WorkManager's watchdog is scheduled only from the UI process (WorkManager is not initialized in :hub).

Closing or swiping the UI should leave :hub running. Settings force-stop still kills both processes until you open the app or use Default SMS wake.

Wake a phone that went offline

Cascade: SSE / WS → SMS wake → adaptive poll. After pairing, the headless :hub process on the phone owns the foreground service and SMS receivers — closing the UI does not stop the gateway. The phone must have confirmed its E.164 number. SMS wake is sent by an online peer device (same owner, SMS grant) or by SMS_WAKE_EGRESS_URL on the server.

  • Portal → Devices → offline row → Wake (shows wake readiness + number)
  • Or enqueue any remote command / SMS while offline — the hub tries SMS wake automatically when the socket is down
  • Force-stop still kills all package processes (Android policy) — only recoverable if the phone opted into Default SMS app (Settings on Android)
# Manual wake (session cookie / portal)
curl -X POST http://localhost:1084/api/devices/DEVICE_ID/wake \
  -H "Cookie: …"