Video Clip API
The Meraki MV Video Clip API enables programmatic retrieval of precise video segments from your MV cameras using specific timestamps. Available on Gen 2 and Gen 3 cameras with firmware 7.2+.
Overview
Request on-demand video clips without continuous streaming infrastructure. Perfect for:
- Security event documentation
- Incident investigation tools
- Automated alert workflows
- Integration with third-party systems
Key Benefits
- Precision: Request exact time windows (down to the second)
- Speed: Clips generate rapidly and are delivered via temporary URLs
- Simplicity: Standard REST API with Meraki API key authentication
- Flexibility: Up to 5-minute clips per request
Endpoint
GET /devices/{serial}/camera/clip
Retrieves a video clip from a specific camera for a given time range.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
serial |
string | Required. The serial number of the target MV camera |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
startTimestamp |
string | Required. ISO8601-formatted start time (UTC). Example: 2026-03-12T14:30:00.000Z |
endTimestamp |
string | Required. ISO8601-formatted end time (UTC). Must be within 5 minutes of start time. Example: 2026-03-12T14:32:00.000Z |
Request Headers
Authorization: Bearer {api_key}
Accept: application/json
Response
Success (200 OK)
{
"url": "https://spn49.meraki.com/stream/jpeg/snapshot/{token}"
}
The url field contains a temporary URL to the video clip in MPEG-TS format. This URL will expire, so download or stream the content immediately.
Error Responses
| Status Code | Description |
|---|---|
| 400 | Bad Request - Invalid timestamp format or duration exceeds 5 minutes |
| 404 | Not Found - Camera serial not found or not an MV camera |
| 429 | Too Many Requests - Rate limit exceeded |
Example Requests
cURL
curl --request GET \
--url 'https://api.meraki.com/api/v1/devices/Q2XX-XXXX-XXXX/camera/clip?startTimestamp=2026-03-12T14:30:00.000Z&endTimestamp=2026-03-12T14:32:00.000Z' \
--header 'Authorization: Bearer {api_key}' \
--header 'Accept: application/json'
Python
import requests
from datetime import datetime, timedelta
# Configuration
API_KEY = "your_api_key_here"
CAMERA_SERIAL = "Q2XX-XXXX-XXXX"
BASE_URL = "https://api.meraki.com/api/v1"
# Request a 2-minute clip from 5 minutes ago
end_time = datetime.utcnow() - timedelta(minutes=5)
start_time = end_time - timedelta(minutes=2)
# Format timestamps
params = {
"startTimestamp": start_time.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
"endTimestamp": end_time.strftime('%Y-%m-%dT%H:%M:%S.000Z')
}
# Make request
response = requests.get(
f"{BASE_URL}/devices/{CAMERA_SERIAL}/camera/clip",
headers={
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/json"
},
params=params
)
if response.status_code == 200:
clip_url = response.json()["url"]
# Download the clip
video_response = requests.get(clip_url)
with open("clip.ts", "wb") as f:
f.write(video_response.content)
print("Clip downloaded successfully")
else:
print(f"Error: {response.status_code} - {response.text}")
JavaScript (Node.js)
const axios = require('axios');
const fs = require('fs');
const API_KEY = 'your_api_key_here';
const CAMERA_SERIAL = 'Q2XX-XXXX-XXXX';
const BASE_URL = 'https://api.meraki.com/api/v1';
// Request a 2-minute clip from 5 minutes ago
const endTime = new Date(Date.now() - 5 * 60 * 1000);
const startTime = new Date(endTime - 2 * 60 * 1000);
async function getVideoClip() {
try {
// Request clip
const response = await axios.get(
`${BASE_URL}/devices/${CAMERA_SERIAL}/camera/clip`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Accept': 'application/json'
},
params: {
startTimestamp: startTime.toISOString(),
endTimestamp: endTime.toISOString()
}
}
);
const clipUrl = response.data.url;
// Download the clip
const videoResponse = await axios.get(clipUrl, {
responseType: 'arraybuffer'
});
fs.writeFileSync('clip.ts', videoResponse.data);
console.log('Clip downloaded successfully');
} catch (error) {
console.error('Error:', error.response?.status, error.response?.data);
}
}
getVideoClip();
Implementation Guide
Video Format Handling
The API returns MPEG-TS (.ts) files. Depending on your use case, you may need to process these files:
Option 1: Convert to MP4 (Server-Side)
Use FFmpeg to convert for broader compatibility:
ffmpeg -i clip.ts -c copy -bsf:a aac_adtstoasc clip.mp4
Option 2: Stream with HLS.js (Browser)
For web applications, use HLS.js or mux.js to transmux to fMP4:
import Hls from 'hls.js';
const video = document.getElementById('video');
const hls = new Hls();
hls.loadSource(clipUrl);
hls.attachMedia(video);
Option 3: Backend Proxy Pattern
For the most robust solution:
- Backend calls Meraki API and proxies the .ts stream
- Backend converts to MP4 on-the-fly or serves .ts with proper CORS headers
- Frontend receives a clean URL from your backend
Best Practices
Timing Considerations
- Latency: Allow 30-60 seconds of lag behind live time for clip availability
- Pre/Post Roll: Add buffer time (30s before/after) for event context
- Max Duration: 5 minutes per request; chain multiple requests for longer periods
# Example: Request clip with 30s pre-roll and 30s post-roll
event_time = datetime.fromisoformat("2026-03-12T14:31:00Z")
start_time = event_time - timedelta(seconds=30) # 30s before
end_time = event_time + timedelta(seconds=30) # 30s after
Error Handling
Implement exponential backoff for rate limits:
import time
def request_clip_with_retry(serial, start, end, max_retries=3):
for attempt in range(max_retries):
response = requests.get(
f"{BASE_URL}/devices/{serial}/camera/clip",
headers=headers,
params={"startTimestamp": start, "endTimestamp": end}
)
if response.status_code == 200:
return response.json()["url"]
elif response.status_code == 429:
# Rate limited - wait and retry
wait_time = 2 ** attempt # Exponential backoff
time.sleep(wait_time)
else:
# Other error - don't retry
response.raise_for_status()
raise Exception("Max retries exceeded")
Security
- Never expose API keys client-side: Use a backend proxy
- Validate timestamps: Ensure they're within retention period
- Download immediately: URLs are temporary and will expire
- Sanitize inputs: Validate serial numbers and timestamps before API calls
Integration Examples
Security Incident Workflow
def document_security_incident(camera_serial, incident_time, pre_roll=30, post_roll=30):
"""
Capture video evidence for a security incident.
Args:
camera_serial: MV camera serial number
incident_time: datetime object of when incident occurred
pre_roll: seconds before incident to include
post_roll: seconds after incident to include
Returns:
Path to downloaded video clip
"""
start = incident_time - timedelta(seconds=pre_roll)
end = incident_time + timedelta(seconds=post_roll)
# Request clip
response = requests.get(
f"{BASE_URL}/devices/{camera_serial}/camera/clip",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"startTimestamp": start.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
"endTimestamp": end.strftime('%Y-%m-%dT%H:%M:%S.000Z')
}
)
clip_url = response.json()["url"]
# Download and save
video = requests.get(clip_url).content
filename = f"incident_{incident_time.strftime('%Y%m%d_%H%M%S')}.ts"
with open(filename, "wb") as f:
f.write(video)
return filename
Multi-Camera Coverage
def get_multi_camera_coverage(camera_serials, event_time, duration_mins=2):
"""
Request clips from multiple cameras for the same time window.
Returns:
Dict mapping camera serial to clip URL
"""
clips = {}
start = event_time - timedelta(minutes=duration_mins/2)
end = event_time + timedelta(minutes=duration_mins/2)
for serial in camera_serials:
try:
response = requests.get(
f"{BASE_URL}/devices/{serial}/camera/clip",
headers={"Authorization": f"Bearer {API_KEY}"},
params={
"startTimestamp": start.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
"endTimestamp": end.strftime('%Y-%m-%dT%H:%M:%S.000Z')
}
)
if response.status_code == 200:
clips[serial] = response.json()["url"]
except Exception as e:
print(f"Failed to get clip from {serial}: {e}")
return clips
Requirements
Hardware
- Meraki MV Gen 2 or Gen 3 camera
- Firmware version 7.2 or newer
Authentication
- Valid Meraki Dashboard API key
- API key must have read access to the organization containing the camera
- Generate API key: Dashboard → Organization → Settings → API
Rate Limits
- Standard Meraki API rate limits apply: 5 requests per second per organization
- Use appropriate retry logic and exponential backoff for 429 errors
Troubleshooting
Common Issues
400 Bad Request - "Invalid timestamp format"
- Ensure timestamps are in ISO8601 format with UTC timezone
- Format:
YYYY-MM-DDTHH:MM:SS.000Z - Verify
endTimestampis afterstartTimestamp - Verify duration is 5 minutes or less
404 Not Found
- Verify camera serial number is correct
- Ensure camera is MV (not MS, MR, etc.)
- Check API key has access to the organization
Empty/Corrupt Video File
- Verify timestamps are within the camera's retention period
- Ensure sufficient lag time (30-60s) behind current time
- Check network connectivity during download
Rate Limit (429)
- Implement exponential backoff
- Reduce request frequency
- Consider caching clips if accessed multiple times