Skrunk Switch Controller

Remote control API

Control your own Switch 2 or Switch Go from a script, Home Assistant, OBS, Stream Deck, 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.

Two ways in. Use Local for most things.

There are two ways to reach your rig, and for most setups you want Local.

LocalCloud
Reaches the rigFrom the same computer, or anywhere on the same network.From anywhere, even off your network.
Needs a keyNo. Reads are open, commands use a short local token.Yes, an API key.
SpeedAs fast as you want. It's your own machine.Fine for the odd command.
LimitsNone.500 calls a day.
Where it runsDesktop app (Windows, Mac, Linux).Any app with remote control on.

Same computer or same network, use Local. Somewhere else, use Cloud. An always on overlay, a stream graph, a Stream Deck, or a Home Assistant box on your own network should almost always be on Local. It's free, it's instant, and nothing leaves your machine. Reach for Cloud only when you actually need to control the rig from somewhere else.

Both start off. Turn on the one you need in Social, then Profile, then Advanced. There are two switches there now, Local and Cloud. Unlocking API access, a one time $5 on Buy Me a Coffee, covers both. 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.

Local (recommended)

Turn on Local in the Advanced tab on your desktop app. The app runs a small web server on your own machine at:

http://127.0.0.1:8787

The Advanced tab shows this address and a local token with a copy button. Reading state needs nothing. The token is only for sending commands.

Reading state, no key

curl http://127.0.0.1:8787/state

You get back the same thing as the cloud state read below. Read it as often as you like.

For an overlay or any of the browser pages below, you don't paste a key at all. Just add ?local=1 to the address:

obs-cat.html?local=1
obs-temp-graph.html?local=1

?local=1 is the key for local. There's nothing else to enter. The page reads http://127.0.0.1:8787/state on your own machine.

Sending a command, token in the body

curl -X POST http://127.0.0.1:8787/command \
  -H "Content-Type: application/json" \
  -d '{"token": "lsk_yourtoken", "action": "set_temperature", "args": {"temp": 420}}'

The token goes in the body, not a header. You get {"ok": true} if it worked, or {"ok": false, "error": "..."} with the reason in the same words the app would use. The actions and the rules are exactly the same as Cloud, see the command list.

From another device on your network

The server also answers other devices on the same network, so a phone or a second computer can read http://<the app computer's address>:8787/state and send commands the same way. For example, your phone runs the app and your PC runs the OBS overlay, both at home, with no key and no cloud in between.

Cloud (reach it from anywhere)

Turn on Cloud in the Advanced tab, then make a key. 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.

You only see a key once.

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 a while, 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.

Reading it often without using your calls

If you want live state for an overlay or a dashboard, don't poll GET /v1/state, that uses up your 500 a day. There are two better ways, and neither counts against your daily calls.

Live push. The app pushes the state the moment something changes, with a quiet heartbeat every 20 seconds. Instead of asking over and over, you subscribe once and the updates come to you. Ask for your channel name with your key, then listen on it. This is what the overlays below do, so a rig that's just sitting there costs almost nothing. The overlays only fall back to a read if the push goes quiet for about 25 seconds. If you're writing your own overlay, the overlay files are the working example to copy.

One read from the database. For a single read, or as a fallback, call this. It returns exactly the same thing as GET /v1/state, straight from the database, so it's light 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.

This read is throttled per account. You get at most one answer every few seconds, plus a daily cap. Go faster and you get a small "slow down" response instead of the state. Push overlays never notice this, since they only read on connect and the odd resync. A tight poll loop will, and that's on purpose: if you're polling this once a second, use Local instead, which has no limits.

Sending a command

This is the cloud version. For local, it's the same action and args, sent to http://127.0.0.1:8787/command with your token in the body (see Local).

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. These are the same on Local and Cloud.

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 from cloud commands

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.

Local answers more simply: {"ok": true} or {"ok": false, "error": "..."}.

Limits

Local has no call limits. It's your own machine, read and send as much as you like. The limits below are Cloud only.

Examples

JavaScript

// Local, on the same machine as the app, no key:
async function rigLocal(action, args = {}) {
  const res = await fetch("http://127.0.0.1:8787/command", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ token: "lsk_yourtoken", action, args }),
  });
  return res.json();
}

// Cloud, from anywhere, with a key:
const API = "https://rvhzqvhigfseqgtpkmhl.supabase.co/functions/v1/rig-api";
const KEY = "skr_yourkeyhere";
async function rigCloud(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 rigLocal("set_profile", { profile: 2 });
await rigLocal("set_temperature", { temp: 480 });

Python

import requests

# Local read, no key:
state = requests.get("http://127.0.0.1:8787/state").json()["state"]
print(state["temperature"], "°F")

Home Assistant

If Home Assistant is on the same network as the app, point it at Local, no key and no daily limit:

rest_command:
  rig_temp:
    url: "http://127.0.0.1:8787/command"
    method: post
    content_type: "application/json"
    payload: '{"token": "lsk_yourtoken", "action": "set_temperature", "args": {"temp": {{ temp }}}}'

sensor:
  - platform: rest
    name: Rig temperature
    resource: "http://127.0.0.1:8787/state"
    value_template: "{{ value_json.state.temperature if value_json.online else 'unavailable' }}"
    unit_of_measurement: "°F"
    scan_interval: 5

Away from your network, use the cloud URLs instead: the rig-api command endpoint with a Bearer key, and the database read for the sensor so checking often 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. Set it to Local if OBS is on the same machine as the app, otherwise paste a cloud key. For a readout only, Local or a Read only key is enough; Fire and +10 need Settings + Fire.
  5. Set the hotkeys in Settings, Hotkeys. Search for "Rig".

For tools that read OpenAPI

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