# HTTP APIs

AutoTouch exposes an HTTP API so you can control a device and manage its scripts over the network — the same surface the companion app and the web dashboard use.

Use the ControlPlane API (/v1) for current integrations. The legacy reference describes older releases only; current builds do not implement those mutation endpoints.

# ControlPlane API (/v1)

The API provides sessions, files, scripts, input, jobs and a WebSocket event stream. Start by checking /v1/system/health and /v1/system/capabilities on your installed build.

# Base URL & transport

The server listens on port 8090:

Interface Base URL When available
Loopback http://127.0.0.1:8090 While the AutoTouch ControlPlane service is running; device-local clients only.
LAN https://<device-ip>:8090 Only when Public Network is enabled in ControlPlane settings and TLS is configured.

The LAN listener is HTTPS-only and "fails closed" — if TLS is not configured, it does not start. When using a self-signed certificate, trust that certificate in your client. The examples use curl -k for a local setup where the device identity has been verified; it disables certificate verification. Find <device-ip> with getLocalIP() in a script or in the app's settings.

All responses include permissive CORS headers (Access-Control-Allow-Origin: *, Access-Control-Allow-Private-Network: true) and answer OPTIONS preflight requests with 204.

# Authentication

Protected endpoints expect a bearer token (including calls from custom loopback clients):

Authorization: Bearer <token>

A paired token is tied to a session, normally valid for 12 hours. Use the returned expires_at value, and pair again after expiry or revocation. A LAN client obtains one through a short pairing flow:

  1. Create a pairing code on the device. POST /v1/pairing/code returns a 6-digit code valid for 10 minutes. Over loopback this needs no auth by default; an optional confirmation setting can restrict code creation; over the LAN it requires an existing session with the system.control scope.
  2. Exchange the code for a token. POST /v1/sessions with the code in the body returns a bearer_token.
  3. Use the token in the Authorization header on every subsequent request.
# 1) On the device (or any loopback process), create a pairing code:
curl -sS -X POST http://127.0.0.1:8090/v1/pairing/code \
  -H 'Content-Type: application/json' \
  -d '{"client_name":"My Tool","client_type":"cli"}'
# -> { "request_id": "...", "data": { "pairing_code": "123456", "expires_at": 1720000600, ... } }

# 2) From your LAN client, exchange the code for a bearer token:
curl -ksS -X POST https://192.168.1.99:8090/v1/sessions \
  -H 'Content-Type: application/json' \
  -d '{"pairing_code":"123456","client_name":"My Tool","client_type":"cli"}'
# -> { "request_id": "...", "data": { "bearer_token": "b1a2...uuid", "session": { "expires_at": ..., "scopes": [...] } } }

# 3) Call the API with the token:
curl -ksS https://192.168.1.99:8090/v1/scripts/running \
  -H 'Authorization: Bearer b1a2...uuid'

The companion app handles its own local authentication automatically; custom tools should use the pairing flow above. A computer’s 127.0.0.1 is the computer itself, not the iPhone.

Wrong pairing codes are rate-limited (a 60-second lockout after 5 failures). A session token can be revoked with DELETE /v1/sessions/current.

# Response format

Every JSON response is wrapped in an envelope. Success:

{
    "request_id": "3f2c...",
    "data": { }
}

Error:

{
    "request_id": "3f2c...",
    "error": {
        "code": "auth.unauthorized",
        "message": "A valid bearer token is required.",
        "retryable": false
    }
}

request_id echoes an X-Request-ID request header if you send one (useful for tracing), otherwise it is generated. Common HTTP statuses: 200 OK, 201 created, 202 accepted (async), 400 validation error, 401 unauthenticated, 403 missing scope, 404 not found, 409 conflict / confirmation required, 429 rate-limited.

# Scopes

A session carries a set of scopes that gate what it can do (for example files.read, scripts.execute, input.inject, settings.write). A LAN client paired with the default settings receives a broad set that excludes the most sensitive scopes (system.control and dangerous.confirm). To grant more, pass scope_hints when creating the pairing code. A 403 can indicate a missing scope; inspect error.code and the response details.

# Dangerous actions & confirmation

A few destructive or high-impact actions — injecting input, respringing, deleting files, writing outside the scripts folder, clearing the audit log, toggling the input-text function, enabling remote management — require an extra confirmation step when called over the LAN:

  1. The first call returns 409 with error.code = "dangerous_action.confirmation_required" and a details block.
  2. Send POST /v1/confirmations with {action, details} using error.details.action and error.details.confirmation.details from that response. It requires the dangerous.confirm scope and returns a single-use confirmation_token (default validity: 120 seconds).
  3. Re-send the original request with the header X-AT-Confirmation-Token: <token>.

Loopback requests skip confirmation entirely. Because a default LAN pairing does not include dangerous.confirm, these actions are effectively loopback-only unless you pair with elevated scopes.

# First script request

After pairing, replace the sample address and token below. hello.lua must already exist in the device's scripts directory.

AT_BASE_URL='https://192.168.1.99:8090'
AT_SESSION_TOKEN='replace-with-bearer-token'

# List script files. --data-urlencode handles spaces and non-ASCII paths.
curl -ksS --get "$AT_BASE_URL/v1/files" \
  -H "Authorization: Bearer $AT_SESSION_TOKEN" \
  --data-urlencode 'path='

# Start a script.
curl -ksS -X POST "$AT_BASE_URL/v1/scripts/run" \
  -H "Authorization: Bearer $AT_SESSION_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"path":"hello.lua"}'

# Inspect running scripts; a successful start response is not completion.
curl -ksS "$AT_BASE_URL/v1/scripts/running" \
  -H "Authorization: Bearer $AT_SESSION_TOKEN"

# Stop that script.
curl -ksS -X POST "$AT_BASE_URL/v1/scripts/stop" \
  -H "Authorization: Bearer $AT_SESSION_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"path":"hello.lua"}'

Inspect the HTTP status and JSON body on failure. 401 usually requires a new session; 403 requires checking scopes; 409 may indicate a state conflict or an action confirmation. For a job started via /v1/jobs, poll the returned job ID until its terminal state; 202 Accepted is not completion.

# Endpoints

Success bodies below describe the data field of the envelope. Append these paths to the appropriate base URL above: HTTP for loopback, HTTPS for the enabled LAN listener. In body notation, ? means optional and a | b means either field; these are schema hints, not literal JSON.

# Sessions & pairing

Method Path Scope Purpose
GET /v1/pairing loopback: none · LAN: system.control Current pairing status.
POST /v1/pairing/code loopback: none · LAN: system.control Create a 6-digit pairing code. Optional body {client_name, client_type, scope_hints[]}.
DELETE /v1/pairing/code loopback: none · LAN: system.control Clear the active pairing code.
POST /v1/sessions none (the pairing code is the credential) Exchange {pairing_code, client_name?, client_type?, requested_scopes?} for a bearer_token.
GET /v1/sessions/current any valid token Details of the current session.
DELETE /v1/sessions/current any valid token Revoke the current session.
POST /v1/confirmations dangerous.confirm Issue a confirmation token. Body {action, details, ttl_seconds?}.

# Files

Use paths relative to the scripts directory, such as Records/hello.lua. Path rules depend on the operation; writes outside the scripts directory require additional checks and may be rejected. URL-encode query/path values, especially spaces and non-ASCII names.

Method Path Scope Purpose
GET /v1/files?path= files.read List a directory (defaults to the scripts root).
POST /v1/files files.write Create a file. Body {path}.
DELETE /v1/files?path= files.write Delete a file (dangerous).
GET /v1/files/content?path= files.read Get a file's text content.
PUT /v1/files/content files.write Write a file. Body {path, content}.
GET /v1/files/raw?path= files.read Download raw file bytes (not the JSON envelope).
POST /v1/files/folders files.write Create a folder. Body {path}.
POST /v1/files/move files.write Move/rename. Body {path, new_path}.
POST /v1/files/upload?path= files.write Upload a file; the raw request body is the file bytes.
POST /v1/files/encrypt files.write Encrypt a Lua script or package to .ate. Body {path, password?}.
DELETE /v1/files/dialog-memory?path= files.write Clear remembered dialog values for a script.

# Scripts, auto-launch & daemons

Method Path Scope Purpose
GET /v1/scripts/running scripts.read List running scripts.
POST /v1/scripts/run scripts.execute Start a script. Body {script_id \| path}.
POST /v1/scripts/stop scripts.execute Stop a script. Body {script_id \| path}.
POST /v1/scripts/stop-all scripts.execute Stop all running scripts.
GET/PUT /v1/scripts/{id}/play-settings scripts.read / scripts.write Get or set repeat count, repeat interval and play speed.
GET /v1/autolaunch autolaunch.read List auto-launch scripts.
PUT/DELETE /v1/autolaunch/{id} autolaunch.write Add / remove an auto-launch script.
GET /v1/daemons daemon.read List daemon scripts.
PUT/DELETE /v1/daemons/{id} daemon.write Add / remove a daemon script.

# Jobs (long-running)

Method Path Scope Purpose
POST /v1/jobs jobs.write (+ scripts.execute or recording.write) Start a long-running job. Body {kind:"script.play"\|"recording.session", script_id\|path}.
GET /v1/jobs/{jobId} jobs.read Poll a job's state.
DELETE /v1/jobs/{jobId} jobs.write Cancel a job.

A session can only see and cancel its own jobs unless it holds system.control.

# Input injection

All require scope input.inject and are dangerous-guarded (see above).

Method Path Body
POST /v1/input/touch-down {index, x, y}
POST /v1/input/touch-move {index, x, y}
POST /v1/input/touch-up {index, x, y}
POST /v1/input/key-down {keyCode}
POST /v1/input/key-up {keyCode}
POST /v1/input/text {text}

# Recording

Method Path Scope Purpose
GET /v1/recording recording.read Current recording status.
POST /v1/recording/start recording.write Start recording.
POST /v1/recording/stop recording.write Stop recording.

# System, logs & device

Method Path Scope Purpose
GET /v1/system/info none (sensitive fields need system.read) Device info.
GET /v1/system/health none Health status.
GET /v1/system/capabilities none Supported capabilities.
GET /v1/system/log system.read Runtime log.
DELETE /v1/system/log system.control Clear the runtime log.
GET/DELETE /v1/audit/log audit.read / audit.write Read or clear the audit log.
POST /v1/system/toast system.control Show a toast. Body {message, delay?}.
POST /v1/system/respring system.control Respring (dangerous).
GET/PUT /v1/system/input-text-function system.read / system.control Read or toggle the input-text function.
POST /v1/system/clean-hosts system.control Clean the hosts file.

# Settings, timers & license

Method Path Scope Purpose
GET /v1/settings settings.read All settings.
PATCH /v1/settings settings.write Update multiple settings. Body {values:{...}}.
GET/PUT /v1/settings/{settingId} settings.read / settings.write Get or set one setting.
GET /v1/timers?script_id= timers.read List timers (all, or for one script).
PUT /v1/timers timers.write Create/update a timer. Body {script_id\|path, fire_time \| delay_seconds, repeat?, interval?}.
DELETE /v1/timers?script_id= timers.write Remove a timer.
GET /v1/license/status license.read License status.
POST /v1/license/refresh license.refresh Refresh the license.

# Remote management (legacy Web / WebDAV servers)

Method Path Scope Purpose
GET/PUT /v1/remote-management/http-server remote_management.read / remote_management.write State / toggle of the legacy web file server. Body {enabled?, auto_launch?}.
GET/PUT /v1/remote-management/webdav remote_management.read / remote_management.write State / toggle of the WebDAV server.

# Event stream (WebSocket)

GET /v1/events upgrades to a WebSocket (wss:// over the LAN). Authenticate with the bearer token, either via the Authorization header or Sec-WebSocket-Protocol: autotouch.bearer, <token>. The server pushes typed events such as script.started, script.stopped, job.updated, settings.changed, recording.started, recording.stopped and more.

No screenshot endpoint. The ControlPlane API does not expose screen capture. Use the screenshot() scripting function instead.


# Legacy Web Server API

Historical API

The routes below apply to older AutoTouch builds. Current builds use /v1 for script/file operations; the Web Server serves dashboard/static content and does not implement these old control routes. WebDAV remains a separate file-management service.

Old route Current equivalent
/control/start_playing POST /v1/scripts/run
/control/stop_playing POST /v1/scripts/stop
/files GET /v1/files
/file/newFolder POST /v1/files/folders
/file/new POST /v1/files
/file/delete DELETE /v1/files
/file/rename POST /v1/files/move
Older Web Server request and response examples

These examples use the historical http://192.168.1.99:8080 server. They are not current ControlPlane requests.

# Play a script

GET /control/start_playing?path=/scriptPath

Parameters

Parameter Specification
path Script path.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Script doesn't exist."
}

Examples

HTTP GET http://192.168.1.99:8080/control/start_playing?path=/scriptPath

# Stop playing a script

GET /control/stop_playing?path=/scriptPath

Parameters

Parameter Specification
path Script path.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Script doesn't exist."
}

Examples

HTTP GET http://192.168.1.99:8080/control/stop_playing?path=/scriptPath

# List files in a directory

GET /files?path=/Records

Parameters

Parameter Specification
path Directory path to list.

Return

{
    "files": [
        {
            "filePath": "/Records/2019-03-10 12:00:00.lua",
            "fileSize": "12 KB",
            "iconName": "script"
        }
    ]
}

Examples

HTTP GET http://192.168.1.99:8080/files?path=/Records

# Create a new directory

GET /file/newFolder?path=/Test

Parameters

Parameter Specification
path New directory path to create.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Invalid file path."
}

Examples

HTTP GET http://192.168.1.99:8080/file/newFolder?path=/Test

# Create a new file

GET /file/new?path=/newFilePath

Parameters

Parameter Specification
path New file path to make.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Invalid file path."
}

Examples

HTTP GET http://192.168.1.99:8080/file/new?path=/newFilePath

# Delete a file

GET /file/delete?path=/filePathToDelete

Parameters

Parameter Specification
path File path to delete.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Invalid file path."
}

Examples

HTTP GET http://192.168.1.99:8080/file/delete?path=/filePathToDelete

# Rename a file or directory

GET /file/rename?path=/oldFilePath&newPath=/newFilePath

Parameters

Parameter Specification
path Old path.
newPath New path.

Return

Successful:

{
    "status": "success"
}

Failed:

{
    "status": "fail",
    "info": "Invalid file path."
}

Examples

HTTP GET http://192.168.1.99:8080/file/rename?path=/oldFilePath&newPath=/newFilePath

Top

Last Updated: 3 days ago