Skip to content

REST API

SimDeck serves the browser UI and API from the same HTTP server. Routes under /api/* return JSON unless noted.

Use the CLI for most automation. Use the API when you are building a custom client, test harness, or editor integration.

Authentication

Browser sessions loaded from the SimDeck server receive auth automatically. Direct callers should send the daemon token from simdeck daemon status:

text
X-SimDeck-Token: <token>
Authorization: Bearer <token>

LAN browsers can pair with the printed six-digit code through:

http
POST /api/pair

Successful pairing sets the browser auth cookie and also returns the access token for native clients:

json
{ "ok": true, "accessToken": "<token>" }

Quick Examples

sh
curl -H "X-SimDeck-Token: $SIMDECK_TOKEN" \
  http://127.0.0.1:4310/api/simulators
sh
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-SimDeck-Token: $SIMDECK_TOKEN" \
  -d '{"action":"openUrl","url":"https://example.com"}' \
  http://127.0.0.1:4310/api/simulators/<udid>/action

Server

MethodPathPurpose
GET/api/healthServer health, version-ish runtime settings, stream config
GET/api/metricsVideo, encoder, and client stream counters
GET/api/client-stream-statsRecent client stream reports
POST/api/client-stream-statsSubmit client stream stats
GET/api/stream-qualityCurrent stream quality settings
POST/api/stream-qualityUpdate stream quality settings

See Health & Metrics for details.

Devices

MethodPathPurpose
GET/api/simulatorsList iOS Simulators and Android emulators
POST/api/simulatorsCreate and boot a simulator or emulator
GET/api/simulators/create-optionsList device types and runtimes for create
GET/api/simulators/{udid}/stateGet one device state
POST/api/simulators/{udid}/bootBoot a simulator or emulator
POST/api/simulators/{udid}/shutdownShut it down
POST/api/simulators/{udid}/eraseErase data and settings

Device IDs come from /api/simulators. Android IDs use the android: prefix. Booted devices are listed first. Paired iPhone and Apple Watch entries include pairedWatchUDID or pairedPhoneUDID when CoreSimulator reports a pairing.

Create requests use identifiers from /api/simulators/create-options. New devices are booted before the response is returned. If an iOS simulator is created with pairedWatch, the watch is created, paired, and booted too.

iOS:

json
{
  "platform": "ios",
  "name": "iPhone Air",
  "deviceTypeIdentifier": "com.apple.CoreSimulator.SimDeviceType.iPhone-Air",
  "runtimeIdentifier": "com.apple.CoreSimulator.SimRuntime.iOS-26-4",
  "pairedWatch": {
    "name": "Apple Watch Series 11 (46mm)",
    "deviceTypeIdentifier": "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-11-46mm",
    "runtimeIdentifier": "com.apple.CoreSimulator.SimRuntime.watchOS-26-4"
  }
}

Android:

json
{
  "platform": "android",
  "name": "Pixel_8_API_36",
  "deviceTypeIdentifier": "pixel_8",
  "runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a"
}

Apps

MethodPathBody
POST/api/simulators/{udid}/install{ "appPath": "/path/to/App.app" }
POST/api/simulators/{udid}/install-uploadRaw .ipa or .apk bytes with x-simdeck-filename
POST/api/simulators/{udid}/uninstall{ "bundleId": "com.example.App" }

install-upload is intended for browser clients. iOS simulator uploads must be .ipa archives; Android emulator uploads must be .apk files. Launch apps and open URLs through /api/simulators/{udid}/action with { "action": "launch", "bundleId": "com.example.App" } or { "action": "openUrl", "url": "https://example.com" }.

Performance

iOS simulator app processes run as host macOS processes. These endpoints expose host-process telemetry for matching simulator app PIDs.

MethodPathPurpose
GET/api/simulators/{udid}/processesList app, extension, helper, and web-content PIDs
GET/api/simulators/{udid}/performanceCurrent sample plus rolling CPU/memory/disk/network history
GET/api/simulators/{udid}/processes/{pid}/performancePerformance data for one simulator app process
POST/api/simulators/{udid}/processes/{pid}/sampleCapture a short CPU stack sample with sample

Performance query parameters:

ParameterNotes
pid=123Select a process; defaults to the foreground app
windowMs=...History window, clamped between 10 seconds and 10 minutes
seconds=3Stack sample duration for POST .../sample

Live Video

MethodPathPurpose
POST/api/simulators/{udid}/webrtc/offerWebRTC offer/answer stream setup
GET/api/simulators/{udid}/h264H.264 WebSocket fallback
GET/api/simulators/{udid}/inputInput WebSocket for fallback transport
GET/api/simulators/{udid}/controlAlias for input control WebSocket
POST/api/simulators/{udid}/refreshRequest a fresh frame or keyframe

For normal clients, copy the browser behavior instead of hand-coding a raw decoder. The UI supports WebRTC first and H.264 WebSocket fallback.

Minimal WebRTC request:

json
{
  "type": "offer",
  "sdp": "v=0...",
  "streamConfig": {
    "profile": "balanced",
    "fps": 60,
    "videoCodec": "auto"
  }
}

Response:

json
{
  "type": "answer",
  "sdp": "v=0..."
}

Actions And Input

MethodPathBody
POST/api/simulators/{udid}/actionOne tagged action or batch payload

Common action bodies:

json
{ "action": "tap", "selector": { "text": "Continue" }, "waitTimeoutMs": 5000 }
json
{
  "action": "tap",
  "selector": { "id": "com.apple.settings.screenTime" },
  "expect": { "selector": { "id": "BackButton" }, "timeoutMs": 5000 }
}
json
{ "action": "back", "timeoutMs": 5000 }
json
{ "action": "touch", "x": 0.5, "y": 0.5, "phase": "began" }
json
{
  "action": "edgeTouch",
  "x": 0.5,
  "y": 0.98,
  "phase": "began",
  "edge": "bottom"
}
json
{
  "action": "multiTouch",
  "x1": 0.35,
  "y1": 0.5,
  "x2": 0.65,
  "y2": 0.5,
  "phase": "began"
}
json
{ "action": "keySequence", "keyCodes": [11, 8, 15], "delayMs": 5 }
json
{ "action": "button", "button": "lock", "durationMs": 50 }
json
{
  "action": "batch",
  "steps": [
    { "action": "tap", "selector": { "text": "Continue" } },
    { "action": "waitFor", "selector": { "text": "Done" }, "timeoutMs": 5000 }
  ]
}

Supported action tags include tap, query, waitFor, assert, assertNot, scrollUntilVisible, touch, touchSequence, edgeTouch, multiTouch, swipe, gesture, type, key, keySequence, button, crown, home, back, dismissKeyboard, appSwitcher, rotateLeft, rotateRight, toggleAppearance, launch, openUrl, describe, and batch. Touch, edge-touch, swipe, gesture, and multi-touch coordinates are normalized from 0.0 to 1.0.

UI State And Inspection

MethodPathPurpose
GET/api/simulators/{udid}/accessibility-treeCurrent UI tree
GET/api/simulators/{udid}/accessibility-point?x=120&y=240Element at a point
POST/api/simulators/{udid}/actionQuery, wait, assert, or batch
POST/api/simulators/{udid}/inspector/requestCall an in-app inspector method

Tree query parameters:

ParameterValues
sourceauto, nativescript, react-native, flutter, swiftui, uikit, native-ax, android-uiautomator
maxDepthInteger depth limit
includeHiddentrue or false
interactiveOnlytrue keeps actionable elements plus their ancestors

Point query parameters:

ParameterValues
x, yRequired screen-point coordinates
maxDepthOptional integer depth limit for native AX output

Every tree response reports the source used and may include a fallbackReason.

Selector actions accept compact accessibility selectors:

json
{
  "selector": {
    "text": "Continue",
    "id": "continue-button",
    "elementType": "Button",
    "enabled": true,
    "regex": false
  },
  "source": "auto",
  "maxDepth": 8,
  "limit": 20
}

Selectors can match text, id, label, value, elementType, index, enabled, checked, focused, and selected. Set regex: true to use regular expression matching for string fields. index uses the same zero-based traversal order behind agent refs, so CLI @e3 maps to API selector { "index": 2 }.

POST /api/simulators/{udid}/action with { "action": "query", ... } returns compact matches. waitFor and assert use the same body shape for positive checks. assertNot performs negative checks.

scrollUntilVisible scrolls and polls until a selector appears:

json
{
  "action": "scrollUntilVisible",
  "selector": { "text": "Settings" },
  "direction": "down",
  "timeoutMs": 10000
}

direction accepts up, down, left, and right.

DevTools And WebKit

MethodPathPurpose
GET/api/simulators/{udid}/webkit/targetsInspectable Safari or WKWebView targets
GET/api/simulators/{udid}/webkit/targets/{targetId}/socketWebKit inspector WebSocket
GET/webkit-inspector-ui/Main.htmlWebInspectorUI frontend
GET/api/simulators/{udid}/devtools/targetsReact Native, app runtime, Metro, or Chrome targets
GET/api/simulators/{udid}/devtools/targets/{targetId}/socketDevTools WebSocket
GET/chrome-devtools-ui/inspector.htmlChrome DevTools frontend

For app-owned WKWebView on iOS 16.4 or newer, the app must set isInspectable = true.

Evidence And Chrome

MethodPathPurpose
GET/api/simulators/{udid}/screenshot.pngPNG screenshot, with ?bezel=true for chrome
POST/api/simulators/{udid}/screen-recordingMP4 recording with { "seconds": 5 }
POST/api/simulators/{udid}/screen-recording/startStart MP4 recording and return recordingId
POST/api/simulators/{udid}/screen-recording/{recordingId}/stopStop recording and return MP4
GET/api/simulators/{udid}/pasteboardGet pasteboard text
POST/api/simulators/{udid}/pasteboardSet pasteboard text with { "text": "hello" }
GET/api/simulators/{udid}/logsRecent logs
GET/api/simulators/{udid}/chrome-profileScreen and chrome geometry
GET/api/simulators/{udid}/chrome.pngRendered device chrome PNG
GET/api/simulators/{udid}/chrome-button/{button}Rendered button sprite
GET/api/simulators/{udid}/screen-mask.pngRendered screen mask PNG

Log query parameters:

ParameterNotes
backfill=trueFetch recent history instead of current tail
seconds=30Time window
limit=250Max entries
levels=error,faultFilter by level
processes=MyAppFilter by process substring
q=LoadedMessage text filter

Inspector Runtime Hub

MethodPathPurpose
GET/api/inspector/connectWebSocket for in-app runtime inspectors
GET/api/inspector/poll?processIdentifier=...Long-poll fallback
POST/api/inspector/requestProtected daemon-to-daemon relay
POST/api/inspector/responseResponse for polled requests

Most clients should call /api/simulators/{udid}/inspector/request instead of these hub routes.

Errors

Errors are JSON:

json
{
  "error": {
    "message": "Unknown simulator 9D7E5BB7-..."
  }
}
StatusCommon cause
400Bad body or query parameter
401Missing or invalid token
404Unknown simulator, target, or asset
408Timed out waiting for a device, stream, or inspector
500Native bridge or server error

Released under the Apache-2.0 License.