InTouch v1 HTTP and stream contract
The local base is http://127.0.0.1:8917. On the Android emulator use http://10.0.2.2:8917. Use HTTPS for remote clients. All API endpoints require Authorization: Bearer <credential> except WebSocket upgrades, which authenticate the first message. The reference node uses one tenant. Browser capture and the API are served from the same origin. A separately hosted browser app should reverse-proxy its API requests through an authorized same-origin integration.
Principals
Device credentials identify exactly one writer. The viewer can read all provisioned devices in this installation. The operator can also provision devices, change configuration and request a flush. Issue your application its own device identity. Keep organization-wide viewer and operator credentials on a trusted backend when building a consumer-facing app. Apply end-user grants to every query and media request. Never embed operator credentials in a shipped client.
| Method and resource | Scope | Request and result |
|---|---|---|
| GET /healthz | Public | Storage readiness, {"status":"ok"} |
| POST /v1/devices | Operator | {"id":"reefer-12"} → 201 with id and generated device token returned once. Persist using platform secure storage. Duplicate ID → 409. |
| POST /v1/devices/{id}/credential | Operator | {"action":"rotate"} issues a replacement and invalidates the prior key. {"action":"revoke"} removes access and retains history. Bootstrap identities rotate through environment configuration. |
| POST /v1/batches | Device | Atomic telemetry batch. 201 first receipt, 200 identical replay, 409 ID with conflicting body. |
| POST /v1/heartbeat | Device | Applied revision → desired revision, full config and at most 16 unexpired commands. |
| PUT /v1/media/{clipId} | Device | Binary MP4 or WebM, up to 16 MiB. Returns ID, bytes and SHA-256. Matching replay is safe. A conflicting MIME or payload returns 409. |
| POST /v1/commands/{id}/ack | Device | Send {} after action completion. Duplicate ACK preserves original timestamp. Expired or foreign pending command → 404. |
| GET /v1/devices | Viewer/operator | devices[] includes id, lastSeen (epoch ms or null), revision, appliedRevision and config. |
| GET /v1/devices/{id}/samples | Viewer/operator | Recent receipts, newest sequence first. limit=1..500 (default 100) and exclusive before sequence. Continue with nextBefore; null ends history. |
| GET /v1/devices/{id}/media | Viewer/operator | Latest 100 clips including ID, MIME, size, server timestamp and SHA-256. |
| GET /v1/devices/{id}/media/{clipId} | Viewer/operator | Authenticated complete clip in its original content type. |
| GET /v1/devices/{id}/commands | Viewer/operator | Latest 50 commands and timestamps for issuance, expiry and receipt of ACK. |
| PATCH /v1/devices/{id}/config | Operator | Send current revision and partial config. Server validates and increments revision atomically. Stale revision → 409; refresh and reconcile edits. |
| POST /v1/devices/{id}/commands | Operator | {"type":"flush"} → unique command ID. Expires after five minutes. A new POST issues a new command. |
| GET /v1/stream | Viewer/operator | Authenticated Server-Sent Events. A change cursor invalidates cached device views. Re-fetch canonical state on reconnect. Heartbeat comment while idle; about a minute per connection. |
| WS /v1/live/publish/{id} | Same device | One live publisher per device. Auth first, then binary JPEG frames. |
| WS /v1/live/watch/{id} | Viewer/operator | Auth first, then receive binary JPEGs while a publisher is connected. |
First device enrollment
Run this from a trusted application backend or an operator terminal. Set INTOUCH_ORIGIN and ADMIN_TOKEN in the environment from your deployment. Save the returned token directly to a protected file or provisioning vault. The callback below refuses redirects so an unexpected redirect cannot forward the credential.
import json, os, urllib.request
class SameEndpoint(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args):
return None
client = urllib.request.build_opener(SameEndpoint)
request = urllib.request.Request(
os.environ["INTOUCH_ORIGIN"].rstrip("/") + "/v1/devices",
data=json.dumps({"id": "greenhouse-probe-12"}).encode(),
headers={"Content-Type": "application/json",
"Authorization": "Bearer " + os.environ["ADMIN_TOKEN"]})
with client.open(request, timeout=10) as response:
device = json.load(response)
# Hand device["token"] to this device's secure provisioning channel.
Batch payload and acknowledgement
{"id":"sensor42-boot7-0019","trigger":"incident","samples":[
{"ts":1794268800000,"kind":"movement","values":{"x":0.2,"y":0.1,"z":0.0,"unit":"m/s2"}},
{"ts":1794268800020,"kind":"movement","values":{"x":13.1,"y":0.3,"z":0.2,"unit":"m/s2"}},
{"ts":1794268800020,"kind":"incident","values":{"type":"acceleration","magnitude":13.105}}
]}
Accepts 1–256 samples and up to 512 KiB of JSON before and after decompression. Optional Content-Encoding: gzip. Use Content-Type: application/json and fixed content length. IDs and channel kinds accept 1–96 alphanumeric, underscore, hyphen or period characters. ts is a nonnegative epoch millisecond integer in the JavaScript safe integer range. values is an object with finite JSON numbers. Triggers are timer, size, manual, incident and control. Each SDK may add optional diagnostic fields.
A receipt includes {"id":"sensor42-boot7-0019","accepted":3,"replayed":false}. Use the same ID and body on retry. Identity is scoped to the authenticated publisher. Samples keep their sensor timestamps even if uploaded out of order. Sequence numbers order insertions. Initial clip upload time is a server receipt time. It does not establish when the scene was captured.
Configuration and control
{"revision":1,"config":{"sampleMs":100,"flushMs":5000,"heartbeatSec":30,"motionThreshold":14.0}}
| Field | Reference default | Allowed |
|---|---|---|
| sampleMs | 200 | 20–60,000 integer |
| flushMs | 15,000 | 1,000–3,600,000 integer |
| heartbeatSec | 45 | 5–3,600 integer |
| motionThreshold (m/s²) | 12 | 0.1–100 |
The device posts {"appliedRevision":0} at first contact. Apply returned settings within OS limits, then post the applied revision on the next heartbeat. Command delivery is at least once before expiry. Flush drains cached readings idempotently. The C host sample leaves heartbeat scheduling and policy changes to the application.
Live preview
Connect over WSS, or WS on loopback. Send text {"token":"<scoped-token>"} within five seconds. Wait for {"ready":true}. Publish complete JPEG messages up to 160 KiB. The server retains only the latest frame in RAM while the publisher is connected, drops intermediate fast frames, pings every 15 s, and ends sessions after 30 min. The browser adapter uses 320×180 JPEG at up to 2 fps and avoids adding to a congested socket. Audio is disabled. Started by the user, the session closes camera tracks when stopped. The authenticated viewer must explicitly open a subscription. The general-purpose C and native phone samples implement stored telemetry and clips according to their SDK guides.
Failures and recovery
Error responses normally contain {"error":"reason"}. A 400 or 415 indicates a payload problem. A 401 calls for reprovisioning. A 403 is a scope error. A 409 signals an ID/revision conflict. A 413 calls for smaller batches or clips. Retry connection failures, 429, 503 and 507 with bounded exponential backoff and jitter. Keep durable items until a matching acknowledgement. On a lost connection after commit, repeating the batch or clip is safe. At overload the bounded listener can close a connection before accepting its body. Database storage is capped separately from client queues.