Skrunk Switch Controller

Remote control API

Control your own Switch 2 or Switch Go from a script, Home Assistant, OBS, or anything else that can send a web request.

The API doesn't talk to the rig directly. Your command goes to the app on your phone or computer, and the app sends it to the rig. So the app has to be open and connected, with remote control turned on. Every command follows the same rules as the buttons in the app. If a button would be greyed out, the command gets refused.

This is for people who know what they're doing. A key that can fire will heat your rig with nobody touching it. Keep keys private, never leave the rig alone while remote control is on, and delete keys you don't use.

Setup

  1. In the app, go to Social, then Profile, then Advanced.
  2. Read the warning and tick the box.
  3. Unlock API access, a one time $5 on Buy Me a Coffee. The Advanced tab shows your friend code and a Buy button. Paste the code when Buy Me a Coffee asks for it, then come back and tap Check. The API is in early access beta, and once it's out of beta, read only access (for things like OBS overlays) may become free.
  4. Turn on On for this device. Only that phone or computer will take commands.
  5. Make a key and copy it. You only see it once.

Keys come in three levels. Give each thing the lowest one it needs.

LevelCan do
Read onlySee the rig's state.
SettingsChange temps, modes, hold time, curves, lights and settings. Can stop a session, can't start one.
Settings + FireAll of that, plus start a session and add time.

Address and key

Every request goes to:

https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api

Send your key in a header:

Authorization: Bearer skr_yourkeyhere

There are three calls. That's the whole API.

CallWhat it does
GET /v1/stateThe rig's current state.
POST /v1/commandSend a command and wait for the answer.
GET /v1/commands/{id}Check on a command you sent earlier.

Reading the state

curl https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api/v1/state \
  -H "Authorization: Bearer skr_yourkeyhere"

You get back something like this. online is false when the app is closed or hasn't checked in for 15 seconds, and state is null then.

{
  "online": true,
  "remote_enabled": true,
  "updated_at": "2026-10-14T03:52:10Z",
  "state": {
    "connected": true,
    "rig": "Switch 2",
    "temperature": 418,
    "target_temp": 420,
    "heating": true,
    "countdown": 24,
    "can_add_time": true,
    "battery": 81,
    "charging": false,
    "profile": 1,
    "mode": "steady",
    "profiles": {
      "1": { "temp": 420, "mode": "steady", "hold_time": 30, "curve": null },
      "2": { "temp": 450, "mode": "custom", "hold_time": 45,
             "curve": [{ "temp": 400, "time": 0 }, { "temp": 460, "time": 20 }] }
    },
    "light_mode": "Grape Ape",
    "brightness": 60,
    "stealth": false,
    "haptic": true,
    "auto_off": 15
  }
}

Temps are always in °F. The app sends a fresh copy about every second.

Reading it often

If you read the state more than every few seconds, like an overlay or a dashboard, use this instead. It returns exactly the same thing, but it reads straight from the database, so it's lighter on the server.

curl -X POST https://rvhzqvhigfseqgtpkmhl.supabase.co/rest/v1/rpc/remote_read_state \
  -H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJ2aHpxdmhpZ2ZzZXFndHBrbWhsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODk0MTU2NzYsImV4cCI6MjEwNDk5MTY3Nn0.2ti9pkZ48gkbDnFranV0R6Kjb8tD90VD4rCMwperqdc" \
  -H "Content-Type: application/json" \
  -d '{"p_key": "skr_yourkeyhere"}'

That long apikey is the server's public key, the same for everyone. Your own key goes in p_key.

Sending a command

curl -X POST https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api/v1/command \
  -H "Authorization: Bearer skr_yourkeyhere" \
  -H "Content-Type: application/json" \
  -d '{"action": "set_temperature", "args": {"temp": 420}}'

The request waits until the app has run it, usually well under a second. If it worked, you get "status": "done" and the rig's state afterwards in result. If the app said no, you get "status": "failed" and the reason in error, in the same words the app would use.

Every command

Anything marked "profile?" works on the active profile if you leave it out, or on profile 1 to 5 if you include it.

ActionArgsKeyRefused when
startFireAlready heating just returns already: true.
add_timeFireNot heating, more than 80s left, or the session can't go any longer.
stopSettingsNot heating just returns already: true.
set_profileprofile 1 to 5SettingsFiring.
set_temperaturetemp 300 to 650, profile?SettingsFiring, or the profile is on Custom.
set_modemode, profile?SettingsFiring.
set_hold_timeseconds 15 to 90, profile?SettingsFiring.
set_curvepoints, profile?SettingsFiring, or the curve breaks the rules below.
set_light_modemode nameSettingsThe rig's firmware doesn't have that light.
set_brightnessvalue 0 to 100SettingsAdvanced Stealth is on.
set_stealthenabled true or falseSettings
set_hapticenabled true or falseSettings
set_auto_offminutes 0 to 60Settings

Every command is also refused while the rig isn't connected, while it's still loading, and while cleaning assist is running.

Modes: steady, ascent, descent, valley, hill, custom.

Lights: Stealth, Purple, Blue, Cyan, Green, Yellow, Orange, Red, Pink, Cali Sunset, Purple Haze, Northern Lights, Vegas Nights, Blue Dream, Strawberry Cough, Florida Groves, Lime Light, Blue Cheese, Golden Goat, Grape Ape, Miami Vice, Purple Urkle. Rigs on the fall 2026 firmware also have Candy Corn, Redrum, Skull & Bones, Witches Brew and Jack-O-Lantern. Capitals and spaces don't matter, so grape ape works.

Curves are 2 to 6 points, each a temp and a time:

{"action": "set_curve", "args": {"points": [
  {"temp": 400, "time": 0},
  {"temp": 450, "time": 10},
  {"temp": 460, "time": 40}
]}}

Times are seconds since the session started, not the length of each step. The first point is at 0, points are at least 1 second apart, and the last one can't be past 90. The heater warms up at most 10°F a second and cools at most 3°F a second, so a curve that asks for more is refused. Same rules as the curve editor in the app. Sending a curve also switches that profile to Custom.

What you get back

CodeMeans
200Done.
202The app took it and is still working on it. Check /v1/commands/{id}.
400Something's wrong with the action or args. message says what.
401No key, wrong key, or the key was deleted.
403The key's level is too low for that.
409The app isn't open with remote control on.
422The app refused it. error says why.
429Too many requests, or the account used its 500 calls for the day. Wait the number of seconds in Retry-After.
504The app never picked it up. It was dropped, not saved for later.

Limits

Examples

JavaScript

const API = "https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api";
const KEY = "skr_yourkeyhere";

async function rig(action, args = {}) {
  const res = await fetch(API + "/v1/command", {
    method: "POST",
    headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ action, args }),
  });
  return res.json();
}

await rig("set_profile", { profile: 2 });
await rig("set_temperature", { temp: 480 });

Python

import requests

API = "https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api"
KEY = "skr_yourkeyhere"

r = requests.get(API + "/v1/state", headers={"Authorization": f"Bearer {KEY}"})
state = r.json()["state"]
print(state["temperature"], "°F")

Home Assistant

Add this to configuration.yaml, then call rest_command.rig_temp from an automation or a button.

rest_command:
  rig_temp:
    url: "https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api/v1/command"
    method: post
    headers:
      Authorization: "Bearer skr_yourkeyhere"
    content_type: "application/json"
    payload: '{"action": "set_temperature", "args": {"temp": {{ temp }}}}'

sensor:
  - platform: rest
    name: Rig temperature
    resource: "https://rvhzqvhigfseqgtpkmhl.supabase.co/rest/v1/rpc/remote_read_state"
    method: POST
    headers:
      apikey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJ2aHpxdmhpZ2ZzZXFndHBrbWhsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODk0MTU2NzYsImV4cCI6MjEwNDk5MTY3Nn0.2ti9pkZ48gkbDnFranV0R6Kjb8tD90VD4rCMwperqdc"
      Content-Type: "application/json"
    payload: '{"p_key": "skr_yourkeyhere"}'
    value_template: "{{ value_json.state.temperature if value_json.online else 'unavailable' }}"
    unit_of_measurement: "°F"
    scan_interval: 10

The sensor uses the database read from Reading the state, so checking every 10 seconds doesn't use up your daily calls.

OBS

obs-rig-control.py adds hotkeys for Fire, Stop, +10 and profiles 1 to 5, and keeps a text source on your scene showing the live temp. It can also stop the rig when your stream or recording ends.

  1. OBS needs Python for scripts. Install Python 3.12 (64 bit) from python.org, then in OBS go to Tools, Scripts, Python Settings and point it at the install folder.
  2. Add a Text source to your scene for the readout.
  3. In Tools, Scripts, click + and pick the file.
  4. Paste your key, pick your text source, and set the format if you want.
  5. Set the hotkeys in Settings, Hotkeys. Search for "Rig".

Use a Read only key if you just want the readout. The hotkeys for Fire and +10 need a Settings + Fire key.

For tools that read OpenAPI

The whole API in the OpenAPI format, for tools like Postman that can import it: openapi.json. No key needed for that one.