# KuruMesaju — API reference (v1)

> Machine-readable: [OpenAPI JSON](https://kurumesaju.rashydh.com/docs/openapi.json) · [Agent index JSON](https://kurumesaju.rashydh.com/docs/api.json) · [HTML docs](https://kurumesaju.rashydh.com/docs)

Base URL: `https://kurumesaju.rashydh.com/api/v1`

All request and response bodies are JSON unless noted. Send `Content-Type: application/json` on POST requests.

## Authentication

### User API (SMS & OTP)

```http
Authorization: Bearer YOUR_API_KEY
```

### Device API (phone / MacroDroid)

```http
Authorization: Bearer YOUR_DEVICE_TOKEN
```

## Phone numbers

**Maldives only.** Maldives mobile numbers (+960) only. Accept national (7711097) or international (+9607711097) input; normalized to E.164 (+960…).

Send `to` or `phone` in national or international form; the server normalizes to E.164-style (`+960` then digits, 8–15 digits total after `+`).

Examples: `+9607711097`, `9607711097`, `7711097`

## Rate limits

Responses may return **429 Too Many Requests** when limits are exceeded.

| Scope | Approx. limit |
|-------|----------------|
| User API (per account) | 60/min |
| POST /sms (per account) | 30/min |
| OTP send + verify (per account) | 10/min |
| Device API (per device) | 240/min |

## User API

### POST /sms

Queue an outbound SMS.

**Request body**

```json
{
  "to": "+9607711097",
  "body": "Hello",
  "device_id": 1
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| to | string | yes | Maldives destination mobile (+960) |
| body | string | yes | Message text (max 4096) |
| device_id | integer | no | Pin job to one device |

**Response 201**

```json
{
  "id": 42,
  "status": "pending",
  "webhook_pushed": true
}
```

`webhook_pushed`: `true` when the server claimed the row and POSTed to the device `webhook_url` in the same request.

**Device auto-selection:** If `device_id` is omitted and the account has exactly one active device with a non-empty `webhook_url`, that device is used automatically.

**Errors:** 401 (bad API key), 422 (validation), 429 (rate limit)

---

### POST /otp/send

Create an OTP and send the code via SMS.

**Request body**

```json
{
  "phone": "+9607711097",
  "device_id": 1,
  "ttl_minutes": 10
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| phone | string | yes | Maldives recipient mobile (+960) |
| device_id | integer | no | Device for SMS leg |
| ttl_minutes | integer | no | 1–60, default 10 |

**Response 201**

```json
{
  "otp_id": 7,
  "sms_message_id": 42,
  "expires_at": "2026-01-01T12:00:00+00:00",
  "webhook_pushed": true
}
```

---

### POST /otp/verify

Verify a 6-digit OTP code.

**Request body**

```json
{
  "phone": "+9607711097",
  "code": "123456"
}
```

**Response 200 (valid)**

```json
{
  "valid": true
}
```

**Response 422 (invalid or expired)**

```json
{
  "valid": false,
  "message": "Invalid or expired code."
}
```

---

## Device API

Base path: `https://kurumesaju.rashydh.com/api/v1/device`

### GET /device/jobs/next

Returns the next pending job or **204 No Content** if the queue is empty.

**Response 200**

```json
{
  "event": "sms_pending",
  "id": 42,
  "sms_message_id": 42,
  "to": "+9607711097",
  "body": "message text"
}
```

Claiming sets `dispatched_at`; the job stays `pending` until you POST a result.

---

### POST /device/jobs/{id}/result

Report delivery outcome. `{id}` is `sms_messages.id`.

**Request body**

```json
{
  "status": "sent"
}
```

Or on failure:

```json
{
  "status": "failed",
  "error": "SIM not ready"
}
```

`status` must be `sent` or `failed`.

**Response 200**

```json
{
  "id": 42,
  "status": "sent"
}
```

**Errors:** 403 (wrong device), 404 (not found), 422 (not dispatched or already finalized)

---

### POST /device/ping

Heartbeat; updates device `last_seen_at` (throttled server-side).

**Response 200**

```json
{
  "ok": true
}
```

---

## Outbound webhook (server → phone)

When a device has `webhook_url`, the server may POST:

```http
POST {device.webhook_url}
Content-Type: application/json
```

```json
{
  "event": "sms_pending",
  "id": 42,
  "sms_message_id": 42,
  "to": "+9607711097",
  "body": "message text"
}
```

After the phone sends the SMS, call `POST /device/jobs/{id}/result`. If the webhook fails, the server may retry; the device can still poll `GET /device/jobs/next`.

## Typical integration flow

1. Operator issues an API key.
2. Your app calls `POST /sms` or `POST /otp/send`.
3. Phone receives job via webhook or polling.
4. Phone sends SMS locally, then `POST /device/jobs/{id}/result`.

## Error shape (validation)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "to": ["Enter a valid destination number."]
  }
}
```
