158 lines
3.3 KiB
Markdown
158 lines
3.3 KiB
Markdown
# Mobile Integration Spec (Stage 1)
|
|
|
|
## Base URL
|
|
|
|
- Processor endpoints are mounted under `/api/v1/mobile`.
|
|
|
|
## Headers
|
|
|
|
- Optional: `x-flow-id` for end-to-end trace correlation.
|
|
- Required for ping/sync endpoints: `x-device-id` with a previously registered `externalDeviceId`.
|
|
|
|
## Endpoints
|
|
|
|
### Register Device
|
|
|
|
- Method: `POST`
|
|
- Path: `/api/v1/mobile/register`
|
|
- Auth: none (trusted private network assumption)
|
|
|
|
Payload:
|
|
|
|
```json
|
|
{
|
|
"externalDeviceId": "android-1234",
|
|
"name": "Pixel 8 Pro",
|
|
"manufacturer": "Google",
|
|
"model": "Pixel 8 Pro",
|
|
"androidVersion": "15"
|
|
}
|
|
```
|
|
|
|
### Ping Device
|
|
|
|
- Method: `PUT`
|
|
- Path: `/api/v1/mobile/ping`
|
|
- Required header: `x-device-id`
|
|
|
|
Payload:
|
|
|
|
```json
|
|
{
|
|
"pingAt": "2026-03-01T10:15:00.000Z"
|
|
}
|
|
```
|
|
|
|
### Sync SMS
|
|
|
|
- Method: `PUT`
|
|
- Path: `/api/v1/mobile/sms/sync`
|
|
- Required header: `x-device-id`
|
|
|
|
Payload:
|
|
|
|
```json
|
|
{
|
|
"messages": [
|
|
{
|
|
"externalMessageId": "msg-1",
|
|
"sender": "+358401111111",
|
|
"recipient": "+358402222222",
|
|
"body": "Hello from device",
|
|
"sentAt": "2026-03-01T10:10:00.000Z",
|
|
"receivedAt": "2026-03-01T10:10:01.000Z",
|
|
"rawPayload": {
|
|
"threadId": "7"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Sync Media Assets
|
|
|
|
- Method: `PUT`
|
|
- Path: `/api/v1/mobile/media/sync`
|
|
- Required header: `x-device-id`
|
|
- Content-Type: `multipart/form-data`
|
|
|
|
Upload contract:
|
|
|
|
- One request uploads one raw media file.
|
|
- Multipart field `file` is required (binary).
|
|
- Optional multipart metadata fields:
|
|
- `externalMediaId`
|
|
- `mimeType`
|
|
- `filename`
|
|
- `capturedAt` (ISO date string)
|
|
- `sizeBytes` (number)
|
|
- `hash`
|
|
- `metadata` (JSON object string)
|
|
- Optional metadata headers (alternative to multipart fields):
|
|
- `x-media-external-id`
|
|
- `x-media-mime-type`
|
|
- `x-media-filename`
|
|
- `x-media-captured-at`
|
|
- `x-media-size-bytes`
|
|
- `x-media-hash`
|
|
- `x-media-metadata` (JSON object string)
|
|
|
|
## Response Contract
|
|
|
|
Success:
|
|
|
|
```json
|
|
{
|
|
"data": {},
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
Failure:
|
|
|
|
```json
|
|
{
|
|
"data": null,
|
|
"error": {
|
|
"flowId": "uuid",
|
|
"code": "VALIDATION_ERROR",
|
|
"message": "Human message",
|
|
"description": "Actionable description",
|
|
"detail": "Technical detail"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Admin Query Contract
|
|
|
|
- Pagination:
|
|
- `page`: 1-based integer
|
|
- `pageSize`: integer
|
|
- Sorting:
|
|
- `sortBy`: operation-specific
|
|
- `sortOrder`: `asc` or `desc`
|
|
- Paginated response payload:
|
|
- `data`: rows
|
|
- `total`: full row count
|
|
- `page`, `pageSize`, `totalPages`
|
|
|
|
## Dedup Rules
|
|
|
|
- Device:
|
|
- Upsert on unique `externalDeviceId`.
|
|
- SMS:
|
|
- Dedup key #1: `(deviceId, externalMessageId)` when provided.
|
|
- Dedup key #2 fallback: `(deviceId, dedupHash)` where dedup hash is SHA-256 of `(deviceId + sentAt + sender + body)`.
|
|
- Media:
|
|
- Raw file is uploaded first and persisted in `file`.
|
|
- Then one `mobile_media_asset` row is created referencing uploaded `fileId`.
|
|
- Dedup key #1: `(deviceId, externalMediaId)` when provided.
|
|
- Dedup key #2 fallback: `(deviceId, hash)` where hash is client-provided or server-computed SHA-256 of file bytes.
|
|
|
|
## Operator Checklist
|
|
|
|
1. Register a device.
|
|
2. Send ping with `x-device-id` and verify dashboard `lastPingAt` updates.
|
|
3. Sync SMS and verify device detail `SMS` tab updates (polling every 5s).
|
|
4. Sync media and verify device detail `Media Assets` tab displays rows.
|