# SMS Bridge — Public API Manual

Base URL: `https://sms.sevenkoapps.store`

All public API calls use API-key auth. Send key + secret via header:

```
x-api-key: <key>
```

Keys grant scopes `send` and/or `read` (created in the dashboard **API Keys** tab, shown once).
Errors are JSON: `{"error":"..."}` with a 4xx/5xx status.

---

## 1. Send an SMS

Queue an outbound message — the connected Android phone delivers it.

```
POST /api/send
x-api-key: <key with send scope>
Content-Type: application/json

{ "to": "+64211234567", "body": "hello", "requestId": "optional-idempotency-id" }
```

| Field | Type | Notes |
|---|---|---|
| `to` | string | E.164-ish, 5–15 digits (`+64211234567`). Non-digits stripped. |
| `body` | string | Required, max 1600 chars. `message` accepted as alias. |
| `requestId` | string | Optional, ≤100 chars; echoed back on the message record. |

**Response `202 Accepted`:**
```json
{ "ok": true, "id": 7, "status": "queued" }
```
`id` is the message id to track delivery via `/api/messages`.

---

## 2. List messages

```
GET /api/messages?direction=in|out&limit=50&offset=0
x-api-key: <key with read scope>
```

| Param | Default | Notes |
|---|---|---|
| `direction` | all | `in` (received) or `out` (sent). |
| `limit` | 50 | Max 500. |
| `offset` | 0 | Pagination offset. |

**Response:**
```json
{
  "messages": [
    {
      "id": 7, "direction": "out", "address": "+64211234567",
      "body": "hello", "status": "delivered", "device_id": 1,
      "api_key_id": 2, "request_id": "optional-idempotency-id",
      "hash": "…", "created_at": "2026-08-22T12:50:00.000Z",
      "updated_at": "2026-08-22T12:51:00.000Z"
    }
  ]
}
```

`status` for outbound: `queued` → `sent` → `delivered` | `failed` (with `error`).
`status` for inbound: `received`.

---

## 3. Health check

```
GET /api/health         → 200 {"ok":true,"service":"sms-bridge","time":"…"}
GET /api/version        → 200 {"versionCode":2,"versionName":"1.1.0","notes":"…","url":"/apk/latest.apk"}
```

---

## 4. APK download (no auth)

```
GET /apk/latest.apk     → application/vnd.android.package-archive
```

---

## 5. Android device endpoints (internal)

These are used by the **SMS Bridge app** — no API key, uses the device's own
auto-generated key. Header: `x-device-key: <key>`. The server auto-registers the
device on first contact.

| Endpoint | Method | Purpose |
|---|---|---|
| `/api/device/pending` | GET | Poll for queued outbound messages → `{"pending":[{id,address,body}]}` |
| `/api/device/report` | POST | Report delivery `{"results":[{"id":7,"status":"delivered","error":"?"}]}` |
| `/api/device/incoming` | POST | Push received SMS `{"messages":[{"from":"+64…","body":"hi","timestamp":"iso"}]}` → `{"accepted":[id]}` |
| `/api/device/heartbeat` | POST | Heartbeat + battery `{"battery":0.5,"charging":true,"appVersion":"1.1.1"}` |

---

## 6. Inbound webhooks (server → your endpoint)

When the phone receives an SMS, the server POSTs a signed webhook to your
configured URL (dashboard **Webhooks & Scripts** tab → global inbound URL).

**Signature header:** `X-SMSBridge-Signature: sha256=<hex HMAC-SHA256 of raw body using webhook_secret>`
Verify it to confirm the request came from the bridge.

**Global inbound payload (`event: sms.received`):**
```json
{
  "event": "sms.received",
  "id": 12, "from": "+64211234567", "body": "hi",
  "timestamp": "2026-08-22T12:50:00.000Z", "device_id": 1
}
```

**Keyword payload (`event: sms.keyword`)** — when body matches a keyword rule:
```json
{
  "event": "sms.keyword",
  "id": 12, "from": "+64211234567", "body": "STATUS",
  "keyword": "STATUS", "rule_id": 3, "timestamp": "2026-08-22T12:50:00.000Z"
}
```

**Delivery:** retried ×2 (10s, 60s) on non-2xx. A non-2xx target logs to the Event
Log. Keyword rules can also run a server-side script and/or queue an auto-reply.

---

## 7. Admin API (dashboard)

Session-cookie auth: `POST /api/auth/login {"password":"…"}` → cookie; use for
subsequent calls. `POST /api/auth/logout` clears it.

| Endpoint | Method | Purpose |
|---|---|---|
| `/api/admin/state` | GET | Full state: `{keys,devices,keywords,messages,settings:{global_webhook,webhook_secret:"(set)",scripts:[…]},first_run}` |
| `/api/admin/send` | POST | Send as dashboard UI: `{"to","body","requestId"}` |
| `/api/admin/messages` | GET | Messages (dir/limit/offset), no read-scope gate |
| `/api/admin/keys` | GET/POST/DELETE | POST `{"name":..,"scopes":["send","read"]}` → `{"key":…}` (shown once); `DELETE /api/admin/keys/:id` |
| `/api/admin/devices` | GET/POST/DELETE | Devices list; devices self-register; `DELETE /api/admin/devices/:id` |
| `/api/admin/keywords` | GET/POST/DELETE | POST `{"keyword":"STATUS","match_mode":"exact","action_webhook":"","action_script":"","reply_text":"","active":true}` |
| `/api/admin/settings` | POST | `{"global_webhook":"https://…","webhook_secret":"…"}` |
| `/api/admin/scripts` | POST | Save script `{"name":"status.sh","content":"#!/usr/bin/env bash\n…","overwrite":true}` |
| `/api/admin/test-webhook` | POST | `{"url":"https://…"}` fires `{"event":"sms.test",…}` |
| `/api/admin/webhook-log` | GET | `?limit=&offset=` delivery log |

---

## Examples (curl)

```bash
KEY=sbk_xxx   # from dashboard

# send
curl -X POST https://sms.sevenkoapps.store/api/send \
  -H "x-api-key: $KEY" -H "Content-Type: application/json" \
  -d '{"to":"+64211234567","body":"hello from curl"}'

# read recent inbound
curl -H "x-api-key: $KEY" \
  "https://sms.sevenkoapps.store/api/messages?direction=in&limit=20"

# track one message
curl -H "x-api-key: $KEY" "https://sms.sevenkoapps.store/api/messages?limit=1"
```

## Errors

| Code | Meaning |
|---|---|
| 400 | Bad request (invalid `to`, missing/long body, unknown admin payload) |
| 401 | Missing/invalid `x-api-key` or `x-device-key`, or admin not logged in |
| 403 | Key lacks required scope (`send`/`read`) |
| 404 | Unknown endpoint |
| 409 | Script file already exists (pass `overwrite:true`) |
| 202 | Send accepted (queued) |