Sophon

Wire reference

The compact, payload-by-payload spec. All keys snake_case. Every timestamp is ms since epoch unless noted.

Envelope

Every JSON response uses the same envelope:

{
  "ok": true,
  "result": { /* per-route */ }
}

On error:

{
  "ok": false,
  "error": {
    "code": "session_not_found",
    "message": "optional human copy",
    "errors": [
      {
        "path": "attachments.0.size",
        "code": "too_big",
        "message": "Number must be less than or equal to 26214400"
      }
    ],
    "retry_after_ms": 1500
  }
}

errors[] is present on validation failures (400 invalid_request) and absent or empty otherwise. See Errors & rate limits for the full code table and rate-limit headers.

REST — bridge namespace

/v1/bridge/sendMessage

{
  "session_id": "ses_…",
  "interaction_id": "int_…?",
  "text": "agent reply text",
  "attachments": [{ "key": "u/…", "mime": "image/png", "size": 1024, "name": null }],
  "reply_to": "msg_…?",
  "idempotency_key": "uuid",
  "usage": {
    "input_tokens": 12,
    "output_tokens": 34,
    "estimated_cost_usd": 0.0012,
    "model": "claude-sonnet-4-5",
    "provider": "anthropic"
  }
}

/v1/bridge/sendMessageDelta

{ "message_id": "msg_…", "delta": "next chunk", "idempotency_key": "uuid" }

/v1/bridge/sendMessageEnd

{
  "message_id": "msg_…",
  "text": "final canonical text (optional — server keeps the streamed text otherwise)",
  "usage": { "...": "..." },
  "finish_reason": "stop | length | content_filter | tool_call",
  "idempotency_key": "uuid"
}

/v1/bridge/createTask / updateTask / finishTask — see Tool calls & approvals.

/v1/bridge/requestApproval — same.

REST — user namespace

POST /v1/me/sessions/:id/send

{
  "text": "user message",
  "attachments": [{ "key": "u/…", "mime": "image/png", "size": 1024, "name": null }],
  "reply_to": "msg_…?",
  "thought_level": "default | extended | max"
}

POST /v1/me/sessions/:id/archive / unarchive — empty body.

PATCH /v1/me/sessions/:id{ title: string | null }.

DELETE /v1/me/sessions/:id — soft-delete with 24 h grace.

PATCH /v1/me/installations/:id

{
  "custom_display_name": "Work Mac" | null,
  "custom_emoji": "🦞" | null
}

POST /v1/me/approvals/:id

{ "decision": "approve | deny | approve_always", "scope": "tool", "scope_value": "Bash" }

SSE — /v1/me/stream

Each event line follows the standard EventSource format:

id: 42
event: message_delta
data: {"session_id":"ses_…","message_id":"msg_…","delta":"hello","interaction_id":"int_…","ts":1730000000000}

Last-Event-ID on reconnect replays the 5-minute / 256-event ring buffer. iOS sends it automatically.

WebSocket — /v1/bridge/ws

server → bridge:
  { "type": "ready", "installation_id": "inst_…" }
  { "type": "update", "update": { "update_id": "1", "type": "session.message", "payload": {...}, "session_id": "ses_…", "interaction_id": "int_…", "installation_id": "inst_…", "created_at": "iso" } }
  { "type": "ping" }

bridge → server:
  { "type": "ack", "up_to_update_id": "12" }
  { "type": "pong" }

Authentication: Authorization: Bearer inst_…:s_… header during upgrade.