Tweaks Protocol

Read live Compose values, change them, reset defaults, and subscribe to updates through the Snap-O Tweaks HTTP and event-stream protocol.

Protocol overview

The Tweaks module exposes HTTP over an app-local abstract Unix-domain socket. Snap-O, custom clients, and authorized agents connect through ADB forwarding. The server does not listen on an Android TCP port or a public network interface, and does not require the INTERNET permission.

Terminal · discover and connect
serial=emulator-5554
socket=snapo_tweaks_12345

adb -s "$serial" shell cat /proc/net/unix
port="$(adb -s "$serial" forward tcp:0 "localabstract:$socket")"
base="http://127.0.0.1:$port"

curl -fsS "$base/tweaks"

Socket names follow snapo_tweaks_<pid>. Keep the forward alive while your client uses it, and discover and forward the new socket again when the Android app process restarts. When finished, remove only the forward you created:

Terminal · remove your ADB forward
adb -s "$serial" forward --remove "tcp:$port"

The Snap-O CLI manages this discovery and cleanup automatically. For a remote ADB server, an ADB forward is local to the server’s host; tunnel it to your client, or use the CLI with both --adb-host and --adb-port for direct ADB transport.

Debug builds enable the server by default. A nondebuggable app can enable it only by including the real Tweaks dependency and setting snapo.tweaks.allow_release to true in its application's <application> metadata. Release no-op artifacts remain the recommended default. See release setup for the manifest example. Network Inspector has its own independent snapo.network.allow_release opt-in.

When its optional dependency is installed and its developer setting is enabled, the on-device floating panel observes the same tweak registry directly. Changes from the panel, Snap-O, and custom clients update that shared registry without a separate panel-specific HTTP connection. Host applications observe updates through the event stream or their normal refresh behavior. See the floating overlay setup for Android integration.

Method Endpoint Purpose
GET /app Read the app name and package name.
GET /app/icon Read the app icon.
GET /tweaks List currently composed tweaks and descriptors.
PATCH /tweaks Update or reset one or more live values.
GET /tweaks/events Subscribe to live tweak snapshots.

Composition determines visibility. A tweak can be listed, changed, or streamed only while at least one composable using its name is in composition. Its last edited value is retained within the app process and restored when the same declaration returns.

GET /app

Read the running app’s user-visible name and Android package name. Use this endpoint to identify which app a client has connected to.

Example Request
GET /app HTTP/1.1
Host: 127.0.0.1
Example Response · 200 OK
{
  "name": "Snap-O Tweaks Demo",
  "packageName": "com.openai.snapo.demo.tweaks"
}

GET /app/icon

Read the running app’s icon. Use the response’s Content-Type without assuming a particular image format or size.

Example Request
GET /app/icon HTTP/1.1
Host: 127.0.0.1
Example Response · 200 OK
HTTP/1.1 200 OK
Content-Type: <image media type>

<image bytes>

The server returns 404 Not Found if an app icon cannot be loaded.

GET /tweaks

List the controls currently in composition. Each descriptor includes its full name, value type, default, and current value. Numeric controls also include any configured min, max, and step.

Example Request
GET /tweaks HTTP/1.1
Host: 127.0.0.1
Example Response · 200 OK
{
  "tweaks": [
    {
      "name": "Typography/Font size",
      "type": "int",
      "default": 36,
      "value": 36,
      "min": 16,
      "max": 72,
      "step": 1
    },
    {
      "name": "Colors/Text",
      "type": "color",
      "default": "#18212F",
      "value": "#18212F"
    },
    {
      "name": "Motion/Show",
      "type": "boolean",
      "default": true,
      "value": true
    }
  ]
}

Supported type values are int, float, boolean, color, and string. Colors use #RRGGBB or #RRGGBBAA.

Compose color defaults retain their original color space and precision in the app, while HTTP represents them as sRGB hex. Color.Unspecified appears as #00000000. Updating a color with its reported default restores the original Compose value, including when that value does not round-trip through its sRGB hex form.

Integer values must be whole numbers. Floating-point values may be whole or fractional. When present, a numeric step is relative to min, or to the tweak's default when no minimum is specified. Reusing a name shares one value across active composables; its type, default, and constraints must match everywhere.

PATCH /tweaks

Update one or more registered tweaks in a single request. Send application/json and put the complete tweak names and their new primitive values in a values object.

Example Request
PATCH /tweaks HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Content-Length: 78

{
  "values": {
    "Typography/Font size": 48,
    "Motion/Show": false
  }
}
Example Response · 200 OK
{
  "tweaks": [
    {
      "name": "Typography/Font size",
      "value": 48
    },
    {
      "name": "Motion/Show",
      "value": false
    }
  ]
}

Updates are validated together. Every tweak must exist, match its declared type, and satisfy its numeric bounds and step before any value is changed. A successful response includes only the updated names and values.

Reset to the default

There is no separate reset endpoint. Send the default reported by GET /tweaks through the same update request.

Example Request · Reset
{
  "values": {
    "Typography/Font size": 36,
    "Motion/Show": true
  }
}

GET /tweaks/events

Subscribe to the current full tweak snapshot and subsequent changes as controls are registered, removed, or updated. The response uses server-sent events with the text/event-stream content type.

Example Request
GET /tweaks/events HTTP/1.1
Host: 127.0.0.1
Accept: text/event-stream
Example Response · 200 OK
HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache

event: tweaks
data: {"tweaks":[{"name":"Typography/Font size","type":"int","default":36,"value":36,"min":16,"max":72,"step":1}]}

event: tweaks
data: {"tweaks":[{"name":"Typography/Font size","type":"int","default":36,"value":48,"min":16,"max":72,"step":1}]}

: keep-alive

Each tweaks event contains a complete current snapshot, not a patch. Replace your client’s current control list with the received tweaks array. Lines starting with : are keep-alive comments.

The first event arrives immediately. Later changes made within the same Android main-thread turn are combined into one ordered snapshot, including values changed by the on-device overlay. An omitted tweak has left composition. Slow clients receive the newest complete snapshot instead of accumulating every intermediate state.

Browser clients need a same-origin proxy. The Android server does not provide CORS headers or handle browser OPTIONS preflight. Serve your interface and its forwarded API through the same origin; native and server-side HTTP clients do not have this restriction.

Error responses

Errors return a JSON object containing an error message and the relevant HTTP status.

Example Response · 422 Unprocessable Entity
{
  "error": "Invalid value for Typography/Font size: Value exceeds the maximum."
}
Status Meaning
400 Bad Request Malformed request, invalid JSON, or a missing values object.
404 Not Found Unknown endpoint, unavailable icon, or tweak not currently in composition.
405 Method Not Allowed Unsupported endpoint method. The Allow response header lists valid methods.
408 Request Timeout The request did not complete within the server’s timeout.
413 Payload Too Large The JSON request exceeds the server’s maximum request size.
422 Unprocessable Entity A value has the wrong type, invalid color, or violates its bounds or step.
500 Internal Server Error An unexpected failure occurred while processing a tweak update.
503 Service Unavailable The connection limit was reached, or the Android main thread is unavailable.
504 Gateway Timeout A tweak update timed out waiting for the Android main thread.