Skip to content

Remote API

Amethyst-Sysadmin edited this page Aug 17, 2026 · 58 revisions

Remote API

Howl provides a remote API that can be used to trigger certain functions of the app. This includes changing controls on the main control panel (such as raising the power level on a channel, or toggling mute) and controlling some functions of the player (such as loading a file, or starting and stopping playback).

The API is useful to advanced users and developers who would like to automate certain functions. For example controlling Howl with their own custom scripts, or using Home Assistant to tie functionality to smart home devices such as physical buttons.

Enabling the API

Howl's remote API is disabled by default. Enabling the "Allow remote access" option in Howl's "Settings" tab will allow connections to the API.

Design

Howl uses a "REST style" API where various endpoints can be accessed via HTTP requests to trigger functions. JSON formatting is generally used to provide parameters and to return information. The API uses a simplified design that does not fully adhere to REST principles (e.g. all requests use HTTP POST).

Security

Access to Howl's API is controlled by the "Remote access key" displayed in Howl's "Settings" tab. This must be submitted as a bearer token in the Authorization header in all requests to the API e.g. Authorization: Bearer YOUR_ACCESS_KEY

It's expected that the API will be used for communication between devices on a secure home network. Use on unsecured public networks or over the internet is not recommended, since the access key is sent in plain text, and could easily be obtained by an attacker.

API location

Howl's API is accessed through the local network IP address of the device running Howl (typically your Android phone). The API always uses HTTP port 4695. For example to make a request to the /status endpoint, you would send an HTTP POST request to http://[YOUR_DEVICE_IP]:4695/status

Libraries and example code

A full Python implementation of the Howl API that includes all the endpoints ("howlapi.py") can be found in the "python" folder of the repository. This should be ready for developers to use.

A basic demo of web-based remote control for Howl ("howl_remote.html") can be found in the "remote_control" folder of the repository. It allows users to control an instance of Howl that is running on the same network. It's not particularly polished, being largely AI generated, and is just intended to showcase the sort of thing that could be made using the API.

API responses

Howl responds to all successful API requests with an HTTP 200 OK status code. Unless otherwise noted below, the response body will contain a JSON object representing the current state of the application. This allows you to immediately verify the result of your command (e.g. seeing the new power level after a set_power request).

Howl's standard JSON status response contains a root object with two nested objects: options (representing the main control panel state) and player (representing the player state).

Example success response body:

{
  "options": {
    "power_a": 30,
    "power_b": 36,
    "power_a_limit": 70,
    "power_b_limit": 70,
    "mute": false,
    "auto_increase_power": false,
    "swap_channels": false,
    "freq_range_min": 0.0,
    "freq_range_max": 0.5
  },
  "player": {
    "playing": true,
    "position": 30.5,
    "title": "nice_pattern.hwl",
    "duration": 120.0
  }
}

Response fields:

  • options
    • power_a (Int): Current power level for Channel A (0-200).
    • power_b (Int): Current power level for Channel B (0-200).
    • power_a_limit (Int): The configured power limit for Channel A.
    • power_b_limit (Int): The configured power limit for Channel B.
    • mute (Boolean): True when global mute is active.
    • auto_increase_power (Boolean): True when power auto increase is active.
    • swap_channels (Boolean): True when swap channels is active.
    • freq_range_min (Float): Position of the minimum frequency range slider (0.0 to 1.0).
    • freq_range_max (Float): Position of the maximum frequency range slider (0.0 to 1.0).
  • player
    • playing (Boolean): True when any content is actively playing.
    • position (Float): The current playback position in seconds.
    • title (String): The title or filename of the playback source.
    • duration (Float): Total duration of the current playback source in seconds.

Error responses

If a request fails, Howl responds with an appropriate 4xx or 5xx HTTP status code.

  • 401 Unauthorized: Returned if the Authorization header is missing or the access key is incorrect.
  • 400 Bad Request: Returned if the request parameters are invalid (e.g. missing required fields, invalid file format).
  • 500 Internal Server Error: Returned if an unexpected error occurs on the server while processing the request.

Error responses generated by Howl are returned in JSON format. The response body contains a root object with a nested error object, which includes a message string describing the specific error. Please note that certain errors generated by libraries we use (such as authentication failures) may return error messages in plain text instead of our standard JSON format.

Example error response body:

{
  "error": {
    "message": "Invalid funscript file"
  }
}

Response fields:

  • error
    • message (String): A description of the error that occurred.

Available API endpoints

/status

Retrieves the current status of the application without modifying any state.

Parameters: None.

Example request body:

{}

/set_power

Sets the absolute power level for one or both channels.

Parameters:

  • power_a (Int, Optional): The desired power level for channel A (0-200).
  • power_b (Int, Optional): The desired power level for channel B (0-200).

Example request body:

{
  "power_a": 20,
  "power_b": 35
}

/increment_power

Increases the power level on one or both channels by a specified amount.

Parameters:

  • channel (Int, Required): The channel identifier (0 for Channel A, 1 for Channel B, -1 for both channels).
  • step (Int, Optional): The amount to increase the power by. Set to 0 to use the step size configured in Howl's settings (default 0).

Example request body:

{
  "channel": 0,
  "step": 5
}

/decrement_power

Decreases the power level on one or both channels by a specified amount.

Parameters:

  • channel (Int, Required): The channel identifier (0 for Channel A, 1 for Channel B, -1 for both channels).
  • step (Int, Optional): The amount to decrease the power by. Set to 0 to use the step size configured in Howl's settings (default 0).

Example request body:

{
  "channel": 1,
  "step": 5
}

/set_mute

Sets or toggles the global mute state.

Parameters:

  • value (Boolean, Optional): The desired mute state (true to mute, false to unmute). If the value parameter is omitted, the current mute state will be toggled.

Example request body (Set mute on):

{
  "value": true
}

Example request body (Toggle mute):

{}

/set_swap_channels

Sets or toggles the swap channels feature.

Parameters:

  • value (Boolean, Optional): The desired state (true to enable swap channels, false to disable). If the value parameter is omitted, the current state will be toggled.

Example request body:

{
  "value": true
}

/set_auto_increase

Sets or toggles the auto increase power feature.

Parameters:

  • value (Boolean, Optional): The desired state (true to enable auto increase, false to disable). If the value parameter is omitted, the current state will be toggled.

Example request body:

{
  "value": false
}

/set_freq_range

Adjusts Howl's main frequency range bar control, changing the frequency range that will be used during playback. Setting min to 0.0 and max to 1.0 will use the maximum range offered by the current output.

Parameters:

  • min (Float, Required): Position of the minimum frequency slider (0.0 to 1.0).
  • max (Float, Required): Position of the maximum frequency slider (0.0 to 1.0).

Constraints:

  • max must be greater than min.
  • The difference between max and min must be at least 0.01.

Example request body:

{
  "min": 0.25,
  "max": 0.75
}

/start_player

Starts or resumes playback of the current source.

Parameters:

  • from (Float, Optional): The timestamp (in seconds) to start playback from. Only applicable to finite sources, such as HWL files and funscripts. If omitted, playback starts from the current position.

Example request body:

{
  "from": 10.5
}

/stop_player

Stops or pauses playback (can be resumed later with start_player).

Parameters: None.

Example request body:

{}

/seek

Seeks to a specific timestamp in the current playback source. If the player is already playing, playback continues from the new position. Only finite sources (such as HWL files and funscripts) are seekable.

Parameters:

  • position (Float, Required): The timestamp (in seconds) to seek to.

Example request body:

{
  "position": 45.0
}

/available_activities

Retrieves a list of all available built-in activities. This is useful for discovering valid activity names to use with the /load_activity endpoint.

Parameters: None.

Response: Returns a JSON object with an activities array containing the internal name and display name of each available activity. Does not return our standard status response.

Example response body:

{
  "activities": [
    {
      "name": "CALIBRATION1",
      "display_name": "Calibration 1"
    },
    {
      "name": "CALIBRATION2",
      "display_name": "Calibration 2"
    },
    {
      "name": "CHAOS",
      "display_name": "Chaos"
    },
    {
      "name": "FASTSLOW",
      "display_name": "Fast/slow"
    },

    ...

  ]
}

Response fields:

  • activities (Array): A list of available activities.
    • name (String): The internal identifier for the activity.
    • display_name (String): A human-readable display name for the activity.

Example request body:

{}

/load_activity

Loads one of Howl's built-in activities. Use the /available_activities endpoint to retrieve a list of valid activity names.

Parameters:

  • name (String, Required): The internal name of the activity to load (e.g. "CALIBRATION1" or "CHAOS").
  • play (Boolean, Optional): The initial playback state, true to start playback immediately (default false).

Example request body:

{
  "name": "RELENTLESS",
  "play": false
}

/load_funscript

Loads a funscript (supplied as a string).

Parameters:

  • title (String, Optional): The name of the funscript, to display on the player.
  • loop (Boolean, Optional): Automatically loop when we reach the end (default false).
  • play (Boolean, Optional): The initial playback state, true to start playback immediately (default false).
  • funscript (String, Required): The complete JSON content of the Funscript file as a string.

Example request body:

{
  "title": "Very nice funscript",
  "loop": false,
  "play": true,
  "funscript": "{ \"actions\":[{\"at\":0,\"pos\":0},{\"at\":3200,\"pos\":100},{\"at\":3920,\"pos\":60}, ... ]}"
}

/load_hwl

Loads a binary HWL file (supplied as a Base64 encoded string).

Parameters:

  • title (String, Optional): The name of the HWL file, to display on the player.
  • loop (Boolean, Optional): Automatically loop when we reach the end (default true).
  • play (Boolean, Optional): The initial playback state, true to start playback immediately (default false).
  • hwl (String, Required): The binary content of the HWL file, encoded as a Base64 string.

Example request body:

{
  "title": "greatpattern.hwl",
  "loop": true,
  "play": true,
  "hwl": "SG93bCBmaWxlIGJpbmFyeSBkYXRhIGhlcmU ... "
}

/start_stream

Initialises a real-time streaming pulse source and immediately begins playback. This creates a continuous stream that accepts pulse data via the /stream_pulse endpoint. Calling this endpoint while a stream is already active will replace the existing stream with a new one.

Once the stream is active, you must continuously send pulses to it at appropriate intervals. If no pulses are received for 5 seconds, the stream will be automatically terminated.

The WebSocket API is suggested for streaming usage. High pulse rates will not be possible with the REST API due to the overhead of establishing a new connection with every call to stream_pulse.

Streaming is suggested only for applications that require generating output in real-time. For fixed patterns that are known in advance, use the load_hwl endpoint instead for immunity to network issues during playback. Latency compensation is also not possible in streaming mode, since we do not know the future.

Parameters:

  • pulse_rate_hz (Int, Optional): The number of pulses per second you intend to send to the stream (default 10).
  • buffer_size (Int, Optional): The size of the internal buffer Howl will allocate to store incoming pulses (default 3). Larger buffers provide more resilience against timing jitter, but increase potential latency.
  • interpolate (Boolean, Optional): When set to true, Howl will interpolate between pulses for you to bring the stream's output to its standard 40Hz rate. This results in smoother output (especially at lower pulse rates), but adds 1 pulse worth of additional latency. Default true.
  • title (String, Optional): The title or name of the stream to display on the player.

Constraints:

  • pulse_rate_hz must be between 1 and 40.
  • buffer_size must be between 2 and 40.

Example request body:

{
  "pulse_rate_hz": 20,
  "buffer_size": 5,
  "interpolate": true,
  "title": "My awesome stream"
}

/stop_stream

Cleanly terminates a real-time stream that was previously initiated with start_stream. The stream ends immediately, without playing out any remaining buffer. If you wish to guarantee that the buffer plays out, make a final call to stream_pulse sending silence, and then wait for the total duration of your buffer before calling stop_stream.

A success response is always returned, and just signifies that Howl executed the stop_stream command (even if there was no active stream to stop).

Example request body:

{}

/stream_pulse

Adds one or more pulses to the active stream buffer. This endpoint is designed to be called repeatedly to feed real-time data into a stream that has already been initialised using start_stream.

Typically you will supply one pulse object at a time, at intervals of 1/pulse_rate_hz seconds (the pulse rate you supplied when starting the stream). If your application has data available in advance and you specified a sufficient buffer, you can alternatively opt to deliver multiple pulses at a time (e.g. send 4 at a time to supply a 40Hz stream while only making 10 API requests per second).

Howl does some buffer management for you to provide consistent output. If a pulse does not arrive in time and the buffer is empty, we will repeat the last pulse that was sent. If pulses arrive when the buffer is already full, the oldest pulses in the buffer will be automatically dropped to make space for the new incoming ones. No errors are returned in these cases, this is considered normal behaviour. Despite these measures, for a high quality stream it is important for your application to send pulses with precise and consistent timing.

This endpoint may return a 400 code error with the message "No active stream" if a streaming source is not active in Howl. In this case your application should stop sending pulses to the stream. This can occur even if you did not terminate the stream, for example if the user manually changed to another source in the app.

Note: Since it is designed to be called very frequently and have minimal overhead, this endpoint returns an empty JSON object {} upon success instead of the standard application status response.

Parameters:

  • pulses (Array, Required): One or more pulse objects to add to the stream buffer.
    • ampA (Float): Normalised amplitude for channel A (0.0 to 1.0).
    • ampB (Float): Normalised amplitude for channel B (0.0 to 1.0).
    • freqA (Float): Normalised frequency for channel A (0.0 to 1.0).
    • freqB (Float): Normalised frequency for channel B (0.0 to 1.0).

Example request body:

{
  "pulses": [
    {
      "ampA": 0.75,
      "ampB": 0.5,
      "freqA": 0.5,
      "freqB": 1.0
    }
  ]
}

Alternative WebSocket API

Howl also provides a persistent WebSocket version of its API, which supports exactly the same functionality as the REST API documented above. The WebSocket API is preferred for applications that need to make very frequent requests (for example real-time streaming), since it avoids the overhead of needing to establish a new HTTP connection with each request.

Location and Connection

The WebSocket version of the API runs on port 4696, and is accessed via the local network IP address of the device running Howl (typically your Android phone). A single endpoint /ws is used for all requests.

WebSocket API location: ws://[YOUR_DEVICE_IP]:4696/ws

Authentication

The WebSocket API uses first message authentication, so you only need to authenticate once when you initially connect.

Immediately after connecting, the first message you send over the WebSocket must be a JSON object containing your Howl remote access key:

{
  "api_key": "YOUR_ACCESS_KEY"
}

If successful, Howl will reply with an authentication success message. If the key is incorrect or the format is invalid, Howl will return an error and close the connection.

Sending Commands

Once authenticated, you send commands as JSON text frames. Instead of hitting different URL paths like the REST API, you instead specify the target endpoint and its parameters within the JSON payload. All the endpoints and parameters are the same as for the REST API documented above.

Example command message:

{
  "endpoint": "set_power",
  "params": {
    "power_a": 20,
    "power_b": 35
  }
}

Note: For endpoints that do not require parameters (like status or stop_player), you can simply omit the params field or set it to null.

Receiving Responses

All responses are sent back as JSON messages over the same WebSocket connection. The response payload wraps the standard API response body inside a metadata object that includes an HTTP-like status code and the endpoint name.

Example success response:

{
  "status": 200,
  "endpoint": "set_power",
  "body": {
    "options": { ... },
    "player": { ... }
  }
}

Example error response:

{
  "status": 400,
  "endpoint": "set_freq_range",
  "body": {
    "error": {
      "message": "max must be greater than min"
    }
  }
}

Key Differences to the REST API

  • Port: 4696 (REST uses 4695).
  • Protocol: WebSocket (ws://) instead of HTTP (http://).
  • Connection: Persistent connection; you only authenticate once and can send multiple commands sequentially without reconnecting.
  • Routing: Endpoints are defined via the "endpoint" string field in the JSON payload, rather than distinct URL paths.
  • Responses: Returned as JSON messages containing status, endpoint, and body fields, rather than standard HTTP response codes and raw bodies.

Controlling Howl using Home Assistant

It's possible to set up any of Howl's API functions as actions in Home Assistant. This opens up many interesting possibilities, since they can then be tied to any of the thousands of smart home devices that Home Assistant supports, including sensors, physical buttons etc. Setting up a Home Assistant server is beyond the scope of this document, so we'll only cover what's necessary to integrate Howl.

The best method to add Howl actions is using Home Assistant's RESTful Command integration. This requires adding text to Home Assistant's configuration.yaml file to define each Howl API command you want to use, along with the parameters you want to send.

A simple example is provided below, which adds a "howl_inc_power" action to increase the power level on both channels by 1, and a corresponding "howl_dec_power" action to decrease the power level on both channels by 1.

You would replace "192.168.0.69" with the IP address of your device running Howl, and replace "ABCDEF123456" with your Howl API key.

# existing lines from your configuration.yaml file

rest_command:
  howl_inc_power:
    url: "http://192.168.0.69:4695/increment_power"
    method: post
    content_type: "application/json"
    verify_ssl: false
    headers:
      Authorization: Bearer ABCDEF123456
    payload: '{"channel": -1, "step": 1}'
  howl_dec_power:
    url: "http://192.168.0.69:4695/decrement_power"
    method: post
    content_type: "application/json"
    verify_ssl: false
    headers:
      Authorization: Bearer ABCDEF123456
    payload: '{"channel": -1, "step": 1}'

It's necessary to restart Home Assistant after editing the file. Be very careful with spacing, as if any lines are aligned wrong, it will break.

Once the Howl actions are set up, you will be able to use them when creating automations in the Home Assistant web UI. You should find them listed under "Other actions / RESTful Command" when selecting the action for your automation.