# 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:
- Create a pairing code on the device.
POST /v1/pairing/codereturns 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 thesystem.controlscope. - Exchange the code for a token.
POST /v1/sessionswith the code in the body returns abearer_token. - Use the token in the
Authorizationheader 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:
- The first call returns
409witherror.code = "dangerous_action.confirmation_required"and adetailsblock. - Send
POST /v1/confirmationswith{action, details}usingerror.details.actionanderror.details.confirmation.detailsfrom that response. It requires thedangerous.confirmscope and returns a single-useconfirmation_token(default validity: 120 seconds). - 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