curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "start_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "stop_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "trigger_upload"
}'
// Start recording
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/commands', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'start_recording',
}),
});
const command = await response.json();
import requests
# Start recording
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/commands',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={
'type': 'start_recording',
},
)
command = response.json()
{
"id": "cmd_xyz789",
"device_id": "dev_abc123",
"type": "start_recording",
"status": "pending",
"params": {},
"grant_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"result": null,
"error": null,
"expires_at": "2025-01-20T11:00:00Z",
"delivered_at": null,
"executed_at": null,
"created_at": "2025-01-20T10:55:00Z"
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is already recording"
}
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is not currently recording"
}
}
{
"error": {
"code": "invalid_request",
"message": "Device is offline (last seen 75s ago via wifi). Connect the device and retry."
}
}
{
"error": {
"code": "resource_not_found",
"message": "Device 'dev_abc123' not found"
}
}
Devices
Create Device Command
Send a command to a device to start recording, stop recording, or trigger upload of pending recordings
POST
/
devices
/
{id}
/
commands
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "start_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "stop_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "trigger_upload"
}'
// Start recording
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/commands', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'start_recording',
}),
});
const command = await response.json();
import requests
# Start recording
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/commands',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={
'type': 'start_recording',
},
)
command = response.json()
{
"id": "cmd_xyz789",
"device_id": "dev_abc123",
"type": "start_recording",
"status": "pending",
"params": {},
"grant_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"result": null,
"error": null,
"expires_at": "2025-01-20T11:00:00Z",
"delivered_at": null,
"executed_at": null,
"created_at": "2025-01-20T10:55:00Z"
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is already recording"
}
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is not currently recording"
}
}
{
"error": {
"code": "invalid_request",
"message": "Device is offline (last seen 75s ago via wifi). Connect the device and retry."
}
}
{
"error": {
"code": "resource_not_found",
"message": "Device 'dev_abc123' not found"
}
}
Send a remote command to a bound device. Commands are handed to either a
Bluetooth relay in the mobile app or the next direct WiFi/4G heartbeat. The
current API accepts ordinary commands only when the device has checked in
within the previous 60 seconds; it does not queue them for an offline device.
For factory reset, use Reset Device:
POST /devices/{id}/reset with no request body. factory_reset is a command
response type, not an accepted type on this command-creation endpoint.
Authentication
Requires an API key withdevices:write scope.
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "start_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "stop_recording"
}'
curl -X POST https://api.bota.dev/v1/devices/dev_abc123/commands \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "trigger_upload"
}'
// Start recording
const response = await fetch('https://api.bota.dev/v1/devices/dev_abc123/commands', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'start_recording',
}),
});
const command = await response.json();
import requests
# Start recording
response = requests.post(
'https://api.bota.dev/v1/devices/dev_abc123/commands',
headers={
'Authorization': 'Bearer sk_live_...',
'Content-Type': 'application/json',
},
json={
'type': 'start_recording',
},
)
command = response.json()
Path Parameters
string
required
The device’s unique identifier (e.g.,
dev_abc123).Request Body
string
required
The command type. One of:
start_recording- Start a new recordingstop_recording- Stop the current recordingtrigger_upload- Upload any pending recordings stored on the device
object
Optional command data. These values are stored and forwarded, but the current
released firmware control paths act on the command
type only.number
Reserved maximum recording duration in seconds (1-86400).
boolean
Reserved request to upload after recording stops.
object
Reserved application metadata stored with the command.
string
Unique key, up to 64 characters, used to return the previously created
command for this device. Reuse it only for the same request.
number
Time-to-live for the command in seconds. Command expires if not executed within this time. Default: 300 (5 minutes).
Response
Returns the command object withstatus set to pending.
{
"id": "cmd_xyz789",
"device_id": "dev_abc123",
"type": "start_recording",
"status": "pending",
"params": {},
"grant_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"result": null,
"error": null,
"expires_at": "2025-01-20T11:00:00Z",
"delivered_at": null,
"executed_at": null,
"created_at": "2025-01-20T10:55:00Z"
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is already recording"
}
}
{
"error": {
"code": "resource_already_exists",
"message": "Device is not currently recording"
}
}
{
"error": {
"code": "invalid_request",
"message": "Device is offline (last seen 75s ago via wifi). Connect the device and retry."
}
}
{
"error": {
"code": "resource_not_found",
"message": "Device 'dev_abc123' not found"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Command identifier |
device_id | string | Target device (dev_*) |
type | string | start_recording, stop_recording, or trigger_upload |
status | string | Command status (see Command Lifecycle below) |
params | object | Command parameters |
grant_token | string | Opaque delivery credential; do not parse, log, or persist it outside the relay operation |
result | object | null | Execution result (populated after execution) |
error | object | null | Error details (if failed) |
expires_at | string | null | Expiration timestamp (ISO 8601) |
delivered_at | string | null | When the backend selected the command for a direct or App relay response (ISO 8601) |
executed_at | string | null | When the backend recorded an executed or failed result (ISO 8601) |
created_at | string | Creation timestamp (ISO 8601) |
Command Lifecycle
Commands go through the following states:| Status | Description |
|---|---|
pending | Command created and not yet selected for a delivery response |
delivered | Backend selected the command for a direct heartbeat or App relay response; this does not prove physical-device receipt |
executed | Backend received a successful acknowledgement, or reconciled a start/stop command from later device state |
failed | Backend received a failed acknowledgement, or reconciled a start/stop state mismatch after its grace period |
expired | TTL elapsed while the command was pending or delivered |
cancelled | Command was cancelled through the API |
Notes
- The device must be bound and have a heartbeat less than 60 seconds old when an ordinary command is created.
deliveredcurrently records backend handoff to a delivery response, not a device receipt. Ordinary commands are not automatically redelivered after that handoff.- For BLE-connected devices, the mobile app polls and relays the command.
- For direct devices, the command is included in a WiFi/4G heartbeat response.
- For
trigger_upload,executedmeans the device accepted the upload trigger; use recording state and upload events to track actual file completion.
Was this page helpful?

