This guide explains how to manage Pan-Tilt-Zoom (PTZ) camera operations through the REST API (/rest/v4). You can send commands to control physical camera movements, query positional limits, set up presets, manage tours, and configure home positions.
Prerequisites
Before issuing PTZ commands, ensure you have:
An authenticated session with the Media Server using a bearer token or basic authentication.
The target device ID (
deviceId).
Find a device ID
You can find the deviceId in two ways:
Desktop Client: Open the camera settings and check the Camera ID field in the General tab.
REST API: Send a
GETrequest to/rest/v4/devicesand locate theidstring for your camera in the JSON response.
For more details, see the article How to find device IDs.
Check device PTZ capabilities
Not all cameras support every PTZ command. To check a camera's supported capabilities:
Send a
GETrequest to/rest/v4/devices/{deviceId}.Read the
ptz.capabilitiesfield in the response payload.
NOTE: If ptz.capabilities contains none or is empty, the device does not support PTZ functions. |
Send PTZ commands
Base path for all PTZ endpoints:
/rest/v4/devices/{deviceId}/ptz
When making POST, PATCH, or PUT requests, pass parameters inside a JSON object in the request body with the Content-Type: application/json header.
Continuous movement
-
POST /rest/v4/devices/{deviceId}/ptz/move
Starts continuous camera movement.pan(float, -1.0 to 1.0): Pan speed along the X-axis.tilt(float, -1.0 to 1.0): Tilt speed along the Y-axis.zoom(float, -1.0 to 1.0): Zoom speed.focus(optional float, -1.0 to 1.0): Focus adjustment direction and speed.
DELETE /rest/v4/devices/{deviceId}/ptz/move
Stops continuous movement immediately.
Positional movement and limits
-
GET /rest/v4/devices/{deviceId}/ptz/position
Retrieves the current camera position. Returns coordinates (xPos,yPos,zPos).type(query string): Specifyabsolute(device-defined range) orlogical(range of -180.0 to 180.0).
-
POST /rest/v4/devices/{deviceId}/ptz/position
Moves the device to a specified absolute coordinate.pan(float, -1.0 to 1.0): X-axis target position.tilt(float, -1.0 to 1.0): Y-axis target position.zoom(float, -1.0 to 1.0): Zoom target position.focus(optional float, -1.0 to 1.0): Target focus position.
-
POST /rest/v4/devices/{deviceId}/ptz/relativeMove
Moves the device relative to its current position.pan(float, -1.0 to 1.0): X-axis offset.tilt(float, -1.0 to 1.0): Y-axis offset.zoom(float, -1.0 to 1.0): Zoom offset.focus(optional float, -1.0 to 1.0): Focus offset.
-
POST /rest/v4/devices/{deviceId}/ptz/viewportMove
Moves the camera view to fit a specified viewport bounding box.viewportLeft(float, 0.0 to 1.0): Left bound ratio.viewportTop(float, 0.0 to 1.0): Top bound ratio.viewportRight(float, 0.0 to 1.0): Right bound ratio.viewportBottom(float, 0.0 to 1.0): Bottom bound ratio.aspectRatio(float): Aspect ratio of the target frame.speed(float, 0.0 to 1.0): Movement speed.
-
GET /rest/v4/devices/{deviceId}/ptz/limits
Retrieves minimum and maximum positional limitations.type(query string): Specifyabsoluteorlogical.
PTZ presets
GET /rest/v4/devices/{deviceId}/ptz/presets
Retrieves all configured presets for the device.-
POST /rest/v4/devices/{deviceId}/ptz/presets
Creates a new preset based on the camera's current position.id(optional string): Internal unique preset identifier.name(string): Human-readable display name for the preset.
-
PATCH /rest/v4/devices/{deviceId}/ptz/presets/{id}
Updates the display name of an existing preset.name(string): Updated display name.
DELETE /rest/v4/devices/{deviceId}/ptz/presets/{id}
Deletes the specified preset.-
POST /rest/v4/devices/{deviceId}/ptz/presets/{id}/activate
Moves the camera to the specified preset position.speed(float, 0.0 to 1.0): Speed at which the device transitions to the preset.
PTZ tours
GET /rest/v4/devices/{deviceId}/ptz/tours
Retrieves all configured PTZ tours for the device.-
POST /rest/v4/devices/{deviceId}/ptz/tours
Creates a new PTZ tour.id(string): Internal tour identifier.name(string): Public display name for the tour.-
spots(array of objects): Ordered list of presets in the tour. Each object requires:presetId(string): ID of the preset.stayTimeMs(integer): Pause duration at this preset in milliseconds.speed(float, 0.0 to 1.0): Travel speed to this preset.
DELETE /rest/v4/devices/{deviceId}/ptz/tours/{id}
Deletes the specified tour.GET /rest/v4/devices/{deviceId}/ptz/tours/*/active
Retrieves the tour that is currently running on the device.POST /rest/v4/devices/{deviceId}/ptz/tours/{id}/active
Starts running the specified tour.
Active and home objects
GET /rest/v4/devices/{deviceId}/ptz/activeObject
Retrieves the active PTZ preset or tour. Returns404 Not Foundif no preset or tour is running. Manual camera movement clears the active object.GET /rest/v4/devices/{deviceId}/ptz/homeObject
Retrieves the configured home preset or tour. Returns404 Not Foundif a home object is not defined.-
PUT /rest/v4/devices/{deviceId}/ptz/homeObjectConfigures the home object that the camera automatically returns to after a period of manual inactivity.
type(string): Set topresetortour.objectId(string): The internal ID of the preset or tour.
-
DELETE /rest/v4/devices/{deviceId}/ptz/homeObjectClears the configured home object.
Python example script
The following script authenticates with the Media Server, checks if a device supports PTZ operations, performs a brief pan move, and activates an available preset.
import time
import requests
import urllib3
# Suppress unverified HTTPS request warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Configuration Constants
USERNAME = "local_account_username"
PASSWORD = "local_account_password"
SERVER_URL = "https://<server_ip>:<server_port>"
CAMERA_ID = "<cameraId>"
VERBOSE = False
def check_status(response):
"""Verify if an API request returned HTTP 200 OK."""
if response.status_code == requests.codes.ok:
if VERBOSE:
print(
f"Request successful: {response.request.method} {response.url}\n{response.text}"
)
return True
print(
f"Request failed: {response.request.method} {response.url} -> {response.status_code}\n{response.text}"
)
return False
def request_api(session, base_url, uri, method="GET", **kwargs):
"""Send an authenticated request and return JSON or raw content."""
response = session.request(method, f"{base_url}{uri}", **kwargs)
if not check_status(response):
raise RuntimeError(f"{method} {uri} -> {response.status_code}")
if response.headers.get("Content-Type") == "application/json":
return response.json()
return response.content
def create_session(base_url, username, password, verify=False):
"""Authenticate and return a session loaded with a bearer token."""
session = requests.Session()
session.verify = verify
resp = session.post(
f"{base_url}/rest/v4/login/sessions",
json={"username": username, "password": password, "setCookie": False},
)
resp.raise_for_status()
token = resp.json()["token"]
session.headers["Authorization"] = f"Bearer {token}"
return session, token
def logout(session, base_url, token):
"""Close the active API session."""
session.delete(f"{base_url}/rest/v4/login/sessions/{token}")
def get_ptz_capabilities(session, base_url, device_id):
"""Fetch and return the camera PTZ capability set."""
device = request_api(
session, base_url, f"/rest/v4/devices/{device_id}?_with=id,name,ptz"
)
caps = (device.get("ptz") or {}).get("capabilities", "") or ""
return set(filter(None, caps.split("|")))
def main():
session, token = create_session(SERVER_URL, USERNAME, PASSWORD, verify=False)
try:
# Step 1: Check PTZ capabilities
caps = get_ptz_capabilities(session, SERVER_URL, CAMERA_ID)
if not (caps - {"none"}):
print("Device does not support PTZ operations.")
return
# Step 2: Read current position
print("Fetching device position...")
position = request_api(
session,
SERVER_URL,
f"/rest/v4/devices/{CAMERA_ID}/ptz/position?type=absolute",
)
print(f"Current position: {position}")
# Step 3: Perform continuous pan move for 1 second
print("Panning right for 1 second...")
request_api(
session,
SERVER_URL,
f"/rest/v4/devices/{CAMERA_ID}/ptz/move",
method="POST",
json={"pan": 1.0, "tilt": 0.0, "zoom": 0.0},
)
time.sleep(1)
# Stop movement
request_api(
session,
SERVER_URL,
f"/rest/v4/devices/{CAMERA_ID}/ptz/move",
method="DELETE",
)
# Step 4: Activate a preset if available
presets = request_api(
session, SERVER_URL, f"/rest/v4/devices/{CAMERA_ID}/ptz/presets"
)
if presets:
first_preset = presets[0]
print(f"Moving camera to preset '{first_preset['name']}'...")
request_api(
session,
SERVER_URL,
f"/rest/v4/devices/{CAMERA_ID}/ptz/presets/{first_preset['id']}/activate",
method="POST",
json={"speed": 1.0},
)
else:
print("No presets found for this device.")
finally:
logout(session, SERVER_URL, token)
if __name__ == "__main__":
main()
Comments
0 comments
Article is closed for comments.