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
- In the app, go to Social, then Profile, then Advanced.
- Read the warning and tick the box.
- 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.
- Turn on On for this device. Only that phone or computer will take commands.
- Make a key and copy it. You only see it once.
Keys come in three levels. Give each thing the lowest one it needs.
| Level | Can do |
|---|---|
| Read only | See the rig's state. |
| Settings | Change temps, modes, hold time, curves, lights and settings. Can stop a session, can't start one. |
| Settings + Fire | All 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.
| Call | What it does |
|---|---|
GET /v1/state | The rig's current state. |
POST /v1/command | Send 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.
| Action | Args | Key | Refused when |
|---|---|---|---|
start | Fire | Already heating just returns already: true. | |
add_time | Fire | Not heating, more than 80s left, or the session can't go any longer. | |
stop | Settings | Not heating just returns already: true. | |
set_profile | profile 1 to 5 | Settings | Firing. |
set_temperature | temp 300 to 650, profile? | Settings | Firing, or the profile is on Custom. |
set_mode | mode, profile? | Settings | Firing. |
set_hold_time | seconds 15 to 90, profile? | Settings | Firing. |
set_curve | points, profile? | Settings | Firing, or the curve breaks the rules below. |
set_light_mode | mode name | Settings | The rig's firmware doesn't have that light. |
set_brightness | value 0 to 100 | Settings | Advanced Stealth is on. |
set_stealth | enabled true or false | Settings | |
set_haptic | enabled true or false | Settings | |
set_auto_off | minutes 0 to 60 | Settings |
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
| Code | Means |
|---|---|
| 200 | Done. |
| 202 | The app took it and is still working on it. Check /v1/commands/{id}. |
| 400 | Something's wrong with the action or args. message says what. |
| 401 | No key, wrong key, or the key was deleted. |
| 403 | The key's level is too low for that. |
| 409 | The app isn't open with remote control on. |
| 422 | The app refused it. error says why. |
| 429 | Too many requests, or the account used its 500 calls for the day. Wait the number of seconds in Retry-After. |
| 504 | The app never picked it up. It was dropped, not saved for later. |
Limits
- 500 API calls a day per account, commands and
GET /v1/statetogether. Resets at midnight UTC. The database read in "Reading it often" doesn't count. - 30 commands a minute per key.
- 5 seconds between
startcommands. - 10 keys per account.
- If the app doesn't pick up a command within 6 seconds, it's thrown away. A phone that comes back online later never runs old commands.
- The app shows a note at the top every time a command comes in, and the Advanced tab lists everything from the last 15 minutes. Older commands are deleted.
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.
- 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.
- Add a Text source to your scene for the readout.
- In Tools, Scripts, click + and pick the file.
- Paste your key, pick your text source, and set the format if you want.
- 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.