# API Authentication
Source: https://docs.nomadicml.com/advanced/authentication
How to authenticate with the Nomadic API
# Authentication
To use the Nomadic API, you need to authenticate your requests using an API key. This guide covers obtaining and using API keys with both the SDK and direct HTTP requests.
## Obtaining an API Key
Generate API keys from the Nomadic web platform:
1. Log in to your account at [app.nomadicml.com](https://app.nomadicml.com)
2. Navigate to your profile by clicking your avatar in the top-right corner
3. Select **API Keys** from the menu
4. Click **Generate New Key**
5. Enter a descriptive name for your key
6. Select the expiration period (default is 90 days)
7. Click **Create Key**
The full API key is only shown once when generated. Copy and store it securely. Lost keys cannot be recovered and must be regenerated.
## Using the Python SDK
Initialize the Nomadic client with your API key:
```python theme={null}
from nomadic import NomadicAI
# Basic initialization
client = NomadicAI(api_key="your_api_key")
# With custom configuration
client = NomadicAI(
api_key="your_api_key",
base_url="https://api.nomadic.company_name.com/", # Custom endpoint for VPC setups
timeout=900 # Request timeout in seconds
)
# Verify authentication
auth_info = client.verify_auth()
print("Authentication successful:", auth_info)
```
### Configuration Parameters
| Parameter | Type | Default | Description |
| ---------- | ----- | ----------------------------------- | ------------------------------- |
| `api_key` | `str` | None | Your Nomadic API key (required) |
| `base_url` | `str` | `"https://api-prod.nomadicml.com/"` | API endpoint URL |
| `timeout` | `int` | `900` | Request timeout in seconds |
## Verifying Authentication
Test your API key validity:
**SDK Method:**
```python theme={null}
auth_info = client.verify_auth()
```
## Troubleshooting
**Invalid API Key** - If you receive an `AuthenticationError`:
* Verify you're using the correct API key
* Check if the key has expired or been revoked
```python theme={null}
try:
client = NomadicAI(api_key="your_key")
client.verify_auth()
except AuthenticationError as e:
print(f"Authentication failed: {e}")
```
**Connection Issues** - If unable to connect:
* Verify internet connectivity
* Check for firewall restrictions on outgoing connections
## Next Steps
See practical examples of API usage with the SDK.
Follow a step-by-step guide to using the API.
# Best Practices
Source: https://docs.nomadicml.com/advanced/best-practices
Optimize your usage of Nomadic and get the most from your video analysis
# Best Practices
This guide provides recommendations and best practices to help you get the most out of Nomadic's motion analysis platform.
## Video Capture Guidelines
The quality of your analysis begins with the quality of your video input. Follow these guidelines for optimal results:
### Camera Placement
* **Primary View**: Mount the camera with optimal perspective for your use case
* **Height**: Position at appropriate level for capturing motion (varies by application)
* **Angle**: Adjust angle to capture the area of interest
* **Clear View**: Ensure the camera has an unobstructed view of the scene
### Video Quality
* **Resolution**: Minimum 720p (1280×720), recommended 1080p (1920×1080)
* **Frame Rate**: Minimum 24 fps, recommended 30 fps
* **Bitrate**: Minimum 4 Mbps, recommended 8 Mbps
* **Format**: MP4 with H.264 encoding works best
* **Lighting**: Ensure adequate lighting for clear visibility
* **Weather**: Be aware that heavy rain, snow, or fog can affect analysis accuracy
### Recording Length
* **Optimal Duration**: 5-20 minutes per video for best performance
* **Split Longer Sessions**: For recordings over 30 minutes, consider splitting into multiple videos
* **Context**: Include enough time before and after key events for proper context
## Data Management
Effective data management ensures you can access and use your analysis effectively.
### Video Organization
* **Consistent Naming**: Use a consistent naming scheme for videos (e.g., `YYYY-MM-DD_Operator_Location.mp4`)
* **Metadata**: Add relevant metadata when uploading (operator, equipment, location, etc.)
* **Tagging**: Use tags to categorize videos by purpose, location, or scenario
* **Archiving**: Develop a policy for archiving older videos to manage storage
### Analysis Retention
Consider how long you need to retain analysis data:
* **Short-term (30 days)**: Recent training sessions or evaluations
* **Medium-term (90 days)**: Trend analysis and pattern recognition
* **Long-term (1+ years)**: Historical comparisons and compliance documentation
## API Usage Optimization
When working with the Nomadic API, follow these practices for optimal performance and reliability.
### Rate Limiting
* **Respect Limits**: Stay within the published rate limits (60 requests/minute, 10,000/day)
* **Batch Operations**: Combine multiple operations into fewer API calls when possible
* **Implement Backoff**: Use exponential backoff when receiving rate limit errors
* **Monitor Usage**: Track your API usage to avoid unexpected throttling
### Efficient Requests
* **Pagination**: Use pagination parameters for large result sets
* **Filtering**: Apply server-side filters to reduce data transfer
* **Field Selection**: Request only the fields you need
* **Compression**: Use gzip compression for larger responses
```python theme={null}
# Example of efficient API usage
import requests
API_BASE = "https://api-prod.nomadicml.com/api"
API_KEY = "your_api_key"
headers = {
"X-API-Key": API_KEY,
"Accept-Encoding": "gzip" # Request compression
}
# Use pagination and filters
params = {
"limit": 100, # Page size
"offset": 0, # Starting point
"event_type": "Motion Anomaly", # Filter by type
"fields": "video_id,time,type,severity,description" # Select only needed fields
}
all_events = []
while True:
response = requests.get(
f"{API_BASE}/events",
headers=headers,
params=params
)
data = response.json()
events = data["events"]
if not events:
break
all_events.extend(events)
params["offset"] += params["limit"] # Move to next page
```
### Caching
Implement client-side caching for frequently accessed data:
* **TTL-based Caching**: Cache responses with appropriate time-to-live values
* **Conditional Requests**: Use etags or last-modified headers for validation
* **Local Storage**: Store reference data locally (e.g., event types, DMV rules)
## SDK Best Practices
When using the Nomadic Python SDK, follow these recommendations:
### Environment Setup
* **Virtual Environments**: Use virtual environments to manage dependencies
* **Version Pinning**: Pin the SDK version in your requirements.txt file
* **Configuration Management**: Use environment variables or secure configuration files for API keys
```bash theme={null}
# Example environment setup
python -m venv nomadic-env
source nomadic-env/bin/activate # Or nomadic-env\Scripts\activate on Windows
pip install nomadic==0.1.0
```
### Error Handling
Implement robust error handling:
```python theme={null}
from nomadic import NomadicAI
from nomadicml.exceptions import (
AuthenticationError,
VideoUploadError,
AnalysisError,
NomadicError
)
try:
client = NomadicAI(api_key="your_api_key")
response = client.upload(url)
response = client.analyze(
response['video_id'],
prompt="Find all instances of ego vehicle straddling two lanes",
)
except AuthenticationError:
# Handle authentication issues
print("Authentication failed - check your API key")
except VideoUploadError as e:
# Handle upload-specific errors
print(f"Upload failed: {e}")
except AnalysisError as e:
# Handle analysis-specific errors
print(f"Analysis failed: {e}")
except NomadicError as e:
# Handle all other SDK errors
print(f"An error occurred: {e}")
except Exception as e:
# Handle unexpected errors
print(f"Unexpected error: {e}")
```
## Analysis Interpretation
Getting the most from your analysis requires proper interpretation of the results.
### Context Matters
* **Environmental Factors**: Consider weather, lighting, and environmental conditions
* **Equipment Limitations**: Account for equipment capabilities and characteristics
* **Operation Purpose**: Interpret events in the context of the operational purpose (training, testing, monitoring, etc.)
### Severity Assessment
When evaluating event severity:
* **Low Severity**: Opportunities for improvement, not immediate safety concerns
* **Medium Severity**: Notable issues that should be addressed
* **High Severity**: Critical safety concerns requiring immediate attention
### Trend Analysis
Look beyond individual events to identify patterns:
* **Frequency Analysis**: Track event frequency over time
* **Location Patterns**: Identify problematic locations or scenarios
* **Operator Comparison**: Compare performance across different operators
* **Before/After**: Measure the impact of training or interventions
## Performance Optimization
For systems processing large volumes of videos, consider these optimization strategies:
### Batch Processing
* Process videos in batches during off-peak hours
* Use background workers for upload and analysis tasks
* Implement queuing systems for large workloads
### Resource Management
* Compress videos before upload to reduce bandwidth
* Clean up temporary files after processing
* Implement TTL (time-to-live) policies for stored videos
## Security Best Practices
Protect your data and access with these security measures:
### API Key Management
* **Rotation**: Rotate API keys regularly (every 90 days recommended)
* **Scope Limitation**: Use the minimum required permissions
* **Secure Storage**: Store API keys in secure credential stores, not in code
* **Monitoring**: Monitor API key usage for unusual patterns
### Data Security
* **Encryption**: Ensure data is encrypted in transit and at rest
* **Access Control**: Implement proper access controls for videos and analysis data
* **Data Minimization**: Only store the data you need
* **Retention Policy**: Implement data retention and deletion policies
### Audit Trail
Maintain an audit trail of system activities:
* Log all video uploads and deletions
* Track who accessed analysis results
* Record API key creation and revocation
* Monitor for suspicious activity
## Next Steps
Now that you understand the best practices, explore these advanced topics:
Detailed API documentation
See practical examples of SDK usage.
# Get analysis document
Source: https://docs.nomadicml.com/api-reference/analysis/get-analysis-document
/openapi.json get /api/videos/{video_id}/analyses/{analysis_id}
Return a specific analysis document for a video.
# Get analysis status
Source: https://docs.nomadicml.com/api-reference/analysis/get-analysis-status
/openapi.json get /api/videos/{video_id}/analyses/{analysis_id}/status
Return status for a specific analysis document.
# Read analysis events
Source: https://docs.nomadicml.com/api-reference/analysis/read-analysis-events
/openapi.json get /api/router/v2/query/events/{stream_id}
Read server-sent progress events for a prompt-based analysis started with the analysis start endpoint.
# Start analysis
Source: https://docs.nomadicml.com/api-reference/analysis/start-analysis
/openapi.json post /api/router/v2/query/start
Start a prompt-based analysis over one or more videos and return an event stream id for progress updates.
# Stream analysis
Source: https://docs.nomadicml.com/api-reference/analysis/stream-analysis
/openapi.json post /api/router/v2/query/stream
Start a prompt-based analysis and stream progress events in the same response. Prefer the start/events flow for resumable clients.
# Verify API key
Source: https://docs.nomadicml.com/api-reference/authentication/verify-api-key
/openapi.json post /api/keys/verify
Validate the caller's API key and return the associated user context.
# Get batch analyses in bulk
Source: https://docs.nomadicml.com/api-reference/batches/get-batch-analyses-in-bulk
/openapi.json post /api/batch/{batch_id}/analyses/bulk
Fetch analysis documents for many batch videos in one request.
# Get batch status
Source: https://docs.nomadicml.com/api-reference/batches/get-batch-status
/openapi.json get /api/batch/{batch_id}/status
Return batch progress, status, and associated video pointers.
# Get import job
Source: https://docs.nomadicml.com/api-reference/cloud-imports/get-import-job
/openapi.json get /api/import-jobs/{job_id}
Return metadata for a cloud import job.
# Import GCS objects
Source: https://docs.nomadicml.com/api-reference/cloud-imports/import-gcs-objects
/openapi.json post /api/gcs/upload
Start asynchronous import for videos stored in Google Cloud Storage.
# Import Hugging Face bucket objects
Source: https://docs.nomadicml.com/api-reference/cloud-imports/import-hugging-face-bucket-objects
/openapi.json post /api/hf-buckets/upload
Start asynchronous import for videos stored in a Hugging Face bucket.
# Import S3 objects
Source: https://docs.nomadicml.com/api-reference/cloud-imports/import-s3-objects
/openapi.json post /api/s3/upload
Start asynchronous import for videos stored in S3 or S3-compatible storage.
# List import job videos
Source: https://docs.nomadicml.com/api-reference/cloud-imports/list-import-job-videos
/openapi.json get /api/import-jobs/{job_id}/videos
Return paginated per-video status rows for a cloud import job.
# Create GCS integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/create-gcs-integration
/openapi.json post /api/cloud-integrations/gcs
Create a reusable Google Cloud Storage integration.
# Create Hugging Face bucket integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/create-hugging-face-bucket-integration
/openapi.json post /api/cloud-integrations/hf-bucket
Create a reusable Hugging Face bucket integration.
# Create S3 integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/create-s3-integration
/openapi.json post /api/cloud-integrations/s3
Create a reusable S3 or S3-compatible storage integration.
# Create S3 Storage Transfer integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/create-s3-storage-transfer-integration
/openapi.json post /api/cloud-integrations/s3-storage-transfer
Create an AWS IAM role integration for MCAP cloud ingest.
# Delete cloud integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/delete-cloud-integration
/openapi.json delete /api/cloud-integrations/{integration_id}
Delete a saved cloud integration owned by the caller.
# Get cloud integration
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/get-cloud-integration
/openapi.json get /api/cloud-integrations/{integration_id}
Return metadata for a saved cloud integration.
# Get role-based S3 import setup
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/get-role-based-s3-import-setup
/openapi.json get /api/cloud-integrations/s3-storage-transfer/setup
Return the provider identity and setup details required for role-based S3 import.
# Get role-based S3 import setup
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/get-s3-storage-transfer-setup
get /api/cloud-integrations/s3-storage-transfer/setup
Return the provider identity and setup details required for role-based S3 import.
# List cloud integrations
Source: https://docs.nomadicml.com/api-reference/cloud-integrations/list-cloud-integrations
/openapi.json get /api/cloud-integrations
List saved cloud storage integrations visible to the caller.
# Create folder
Source: https://docs.nomadicml.com/api-reference/folders/create-folder
/openapi.json post /api/folders
Create a personal or organization folder.
# Create or get folder
Source: https://docs.nomadicml.com/api-reference/folders/create-or-get-folder
/openapi.json post /api/folders/create-or-get
Return an existing folder with the same name/scope or create it if missing.
# Get folder by name
Source: https://docs.nomadicml.com/api-reference/folders/get-folder-by-name
/openapi.json get /api/folders/get
Return folder metadata by name and scope.
# Attach stream
Source: https://docs.nomadicml.com/api-reference/livestreams/attach-stream
/openapi.json post /api/live/attach-stream
Record stream metadata and notify Nomadic that a stream was attached.
# Create signed live manifest URL
Source: https://docs.nomadicml.com/api-reference/livestreams/create-signed-live-manifest-url
/openapi.json post /api/live/session/{session_id}/signed-manifest
Create a short-lived signed URL for a live session HLS manifest.
# End live session
Source: https://docs.nomadicml.com/api-reference/livestreams/end-live-session
/openapi.json post /api/live/end-session
End an active live-stream analysis session.
# Get live session
Source: https://docs.nomadicml.com/api-reference/livestreams/get-live-session
/openapi.json get /api/live/live-sessions/{stream_id}/{session_id}
Return details for a single live session.
# List live sessions
Source: https://docs.nomadicml.com/api-reference/livestreams/list-live-sessions
/openapi.json get /api/live/live-sessions
List live sessions visible to the caller.
# Start live session
Source: https://docs.nomadicml.com/api-reference/livestreams/start-live-session
/openapi.json post /api/live/start-session
Start a live-stream ingestion and optional rapid-review analysis session.
# Create MCAP ingest
Source: https://docs.nomadicml.com/api-reference/mcap/create-mcap-ingest
/openapi.json post /api/mcap/create-ingest
Create a signed upload target for a local MCAP source file.
# Get MCAP import job
Source: https://docs.nomadicml.com/api-reference/mcap/get-mcap-import-job
/openapi.json get /api/mcap/import-jobs/{mcap_import_job_id}
Return status for an MCAP cloud import job.
# Get MCAP ingest
Source: https://docs.nomadicml.com/api-reference/mcap/get-mcap-ingest
/openapi.json get /api/mcap/{ingest_id}
Return status and derived video metadata for an MCAP ingest.
# Process MCAP ingest
Source: https://docs.nomadicml.com/api-reference/mcap/process-mcap-ingest
/openapi.json post /api/mcap/{ingest_id}/process
Start processing an uploaded MCAP file into derived videos.
# Start S3 MCAP cloud ingest
Source: https://docs.nomadicml.com/api-reference/mcap/start-s3-mcap-cloud-ingest
/openapi.json post /api/mcap/cloud-ingest/s3
Start MCAP ingest from an existing S3 integration.
# Stitch MCAP views
Source: https://docs.nomadicml.com/api-reference/mcap/stitch-mcap-views
/openapi.json post /api/mcap/{ingest_id}/stitch
Apply explicit front-channel and role metadata to an MCAP ingest.
# Stitch uploaded views
Source: https://docs.nomadicml.com/api-reference/multi-view/stitch-uploaded-views
/openapi.json post /api/multi-view/stitch
Link already-uploaded videos into a multi-view group.
# Upload a video
Source: https://docs.nomadicml.com/api-reference/uploads/upload-a-video
/openapi.json post /api/upload-video
Upload a local video file or register a remote video URL for asynchronous processing.
# Create signed URLs in bulk
Source: https://docs.nomadicml.com/api-reference/videos/create-signed-urls-in-bulk
/openapi.json post /api/video/signed-urls
Create short-lived signed playback URLs for multiple videos.
# Create signed video URL
Source: https://docs.nomadicml.com/api-reference/videos/create-signed-video-url
/openapi.json post /api/video/{video_id}/signed-url
Create a short-lived signed playback URL for a video.
# Delete video
Source: https://docs.nomadicml.com/api-reference/videos/delete-video
/openapi.json delete /api/video/{video_id}
Delete a video owned by or accessible to the caller.
# Get video status
Source: https://docs.nomadicml.com/api-reference/videos/get-video-status
/openapi.json get /api/video/{video_id}/status
Return upload and processing status for a video.
# List videos
Source: https://docs.nomadicml.com/api-reference/videos/list-videos
/openapi.json get /api/my-videos
List videos visible to the caller, optionally scoped to a folder.
# Quickstart
Source: https://docs.nomadicml.com/getting-started/quickstart
This guide will help you get up and running with Nomadic quickly, either through our web platform or using the SDK.
## Using the Web Platform
The fastest way to start using Nomadic is through our web platform at [app.nomadicml.com](https://app.nomadicml.com).
### 1. Create an Account
Sign up for a free account on [app.nomadicml.com/login](https://app.nomadicml.com/login).
Here you can test one of our sample queries, filter to different verticals, or upload your own videos.
| Feature |
Description |
| Menu pane |
Shows which part of the portal you are located in |
| Upload button |
Upload your own videos here |
| Processing Mode |
Thinking - for accurate results, can take a few mins
Fast - for quicker results, takes seconds
|
| Curated examples |
See already curated examples from sample videos |
| Vertical |
Filter to specific application based on your query |
| Query field |
Enter query for your analysis |
| Sample query |
select from a sample query |
| Video selection |
Select the videos to process for this analysis |
| Analyze |
Click to perform analysis |
Now lets run an analysis and view the results.
### 2. Run the Analysis
* Start by entering a query or selecting a sample query.
* Click "Analyze"
* Once analysis is complete, scroll down and click "View Results" to see full results.
The results page shows further details on the analysis. See the table below for the various menu options in the interface.
| Feature |
Description |
| Batch ID |
Unique Batch ID for this query |
| Share result |
Share batch result using this link |
| Copy video/batch ID |
Copy the video ID or batch ID |
| New Analysis |
Run a new analysis on the same set of videos |
| Filter button |
Filter by approved, rejected, pending events |
| Export/save options |
Save your data in csv or json format |
| Approve/Reject button |
Approved - Analysis is correct and results match query.
Rejected - Analysis incorrect
|
| Reasoning trace |
View reasoning for detected event for this analysis |
| Analyze |
Click to perform analysis |
### 3. Review the analysis.
The results pane shows the full details on this analysis.
* Batch reasoning - Summary of the batch analysis for these videos. You can click on a video with a event label and read more about the analysis.
* Event Summary describes what is happening in the scene during the displayed timestamp.
* Reasoning Trace shows how our agents process in the data to provide the results to the user's query.
### Additional Resources
Dig deeper into using Nomadic's tool with custom queries and settings.
Find documentation on more topics.
# Self-Hosted VPC Setup
Source: https://docs.nomadicml.com/getting-started/vpc-setup
Deploy Nomadic in your own AWS or GCP virtual private cloud.
# Self-Hosted Nomadic VPC Setup
Nomadic supports private deployments in customer-controlled AWS and GCP environments. We bring the application, deployment automation, and model-serving configuration; you keep control of the cloud account, network boundaries, IAM policies, logs, storage, and data residency.
The standard setup is intentionally simple: point Nomadic at an existing private network, provide a small set of infrastructure inputs, and we deploy the web app, API, storage, database, and model endpoints with Terraform-backed infrastructure.
VPC deployments can be accessed through your VPN, Direct Connect, Cloud VPN, Cloud Interconnect, private DNS, or any other access pattern your security team already uses. Public ingress is not required.
## Deployment Models
Deploy into a new or existing AWS VPC with an internal ALB, private EC2 app instances, S3 locked to a VPC endpoint, optional DocumentDB, ECR, SSM operations, and SageMaker model endpoints.
Deploy into a GCP project/VPC with Compute Engine backends, HTTPS load balancing, Artifact Registry, Secret Manager, Memorystore Redis, firewall rules, Cloud NAT, and IAP-based admin access.
## What We Need From You
For either cloud, Nomadic only needs the information required to land in your existing network and match your security model.
### AWS Inputs
* AWS account ID, target region, and the IAM role/profile Nomadic should use for deployment.
* Existing VPC ID, VPC CIDR, private subnet IDs, and an S3 Gateway VPC endpoint ID. If the endpoint is missing, we can provision the baseline endpoint prerequisites first.
* Allowed ingress CIDRs for the internal load balancer, usually your VPC CIDR, VPN CIDRs, or corporate network ranges.
* Domain name and optional ACM certificate ARN if you want HTTPS terminated at the internal ALB.
* Whether metadata should run on Amazon DocumentDB or the managed Mongo-compatible database provisioned with the deployment.
* Model-serving preference: AWS SageMaker endpoints, an existing private inference endpoint, or a hybrid configuration.
### GCP Inputs
* GCP project, region, zones, and the service account Nomadic should use for deployment.
* CIDR range for the dedicated Nomadic subnet.
* Domain names and DNS ownership for managed HTTPS certificates.
* Machine sizes for staging and production API workers.
* Secret Manager access policy, Artifact Registry location, and any required firewall allowlists.
* Optional Redis/Memorystore and private service networking requirements.
## AWS: How It Works
Our AWS VPC deployment is backed by Terraform modules that can either create a private-only VPC or attach to an existing customer VPC. The app stack creates an internal Application Load Balancer, routes `/` to the web service and `/api/*` to the backend API, and places the backing instances in private subnets.
Customer data stays inside the customer account. Video objects are stored in an S3 bucket whose policy can be locked to the S3 Gateway VPC endpoint, so reads and writes stay on the private AWS path. The backend uses IAM roles rather than long-lived access keys wherever possible, including role-based S3 imports from customer buckets.
Inference can run next to the app stack through model-specific SageMaker endpoints. We support separating the application layer from model compute so that each model can be deployed, resized, or replaced independently. This is the pattern we use for large VLM endpoints, OCR, segmentation, and related GPU workloads.
### AWS Bringup Flow
1. Confirm whether we are creating a fresh VPC or attaching to an existing VPC.
2. Apply the VPC prerequisite stack if the existing network is missing required endpoints.
3. Apply the Nomadic app stack: internal ALB, private web/API instances, IAM, ECR, S3, logging, and optional DocumentDB.
4. Build and push the model images, then apply the model-specific compute stacks.
5. Deploy the backend container through SSM and verify `/health`, `/api/health`, and model routing from inside the VPC.
In practice, most customer-specific setup is captured in a small Terraform variable file: VPC IDs, subnet IDs, CIDR allowlists, region, instance sizes, tags, and model endpoint names.
## GCP: How It Works
Nomadic also supports GCP VPC deployments with a cloud-native infrastructure pattern. Terraform provisions a dedicated VPC/subnet, places backend VMs in Compute Engine, fronts them with managed HTTPS load balancing, and stores deployable images in Artifact Registry.
The GCP stack includes the pieces needed for a production service: firewall rules for load balancer health checks, optional IAP SSH for operator access, Cloud NAT for controlled outbound access, Secret Manager for runtime secrets, Cloud Logging/Monitoring, Memorystore Redis, and private service networking where needed.
During deployment, we push the backend image to Artifact Registry, start the container with the approved runtime environment, wait for local health, and then wait for the GCP backend service to report healthy.
### GCP Bringup Flow
1. Confirm project, region, DNS names, service accounts, and network boundaries.
2. Apply the Terraform baseline for VPC, subnet, firewall, load balancers, Artifact Registry, Secret Manager, Redis, and IAM.
3. Populate approved secrets in Secret Manager.
4. Run the deploy workflow to install and start the backend container on each VM.
5. Verify managed certificate status, load balancer health, and API health checks.
As with AWS, the deployment is mostly a small variable file: project ID, region, zones, domains, subnet CIDR, machine sizes, Artifact Registry location, and IAM labels. The result is a private, cloud-native GCP deployment that your infrastructure team can inspect and operate using standard GCP controls.
## Security And Operations
* Network ingress is controlled by your VPC, firewall rules, security groups, load balancer configuration, and private connectivity.
* Storage access uses cloud-native identity: AWS instance roles for S3 and GCP service accounts for GCS/Artifact Registry/Secret Manager.
* Model compute is configurable per deployment. AWS customers can use SageMaker-backed endpoints; GCP customers can use approved private inference endpoints or a customer-specific model-serving plan.
* Updates are repeatable because infrastructure is Terraform-managed and application rollout is automated.
## Support
To start a VPC deployment, contact your Nomadic representative or email [support@nomadicml.com](mailto:support@nomadicml.com). We will review your target cloud, network requirements, security constraints, and model-serving needs, then provide the exact Terraform inputs for your environment.
# Nomadic AI Main Services Agreement
Source: https://docs.nomadicml.com/more/main-services-agreement
# Nomadic AI Main Services Agreement
This Nomadic AI Main Services Agreement ("Agreement") is entered into between NomadicML Inc. ("Nomadic AI", "we", "us", or "our") and you or the entity you represent ("Customer" or "you") as of the Effective Date. This Agreement sets forth the terms and conditions under which Customer may access and use the Services.
BY ACCEPTING THIS AGREEMENT, INCLUDING BY EXECUTING AN ORDER FORM THAT REFERENCES THIS AGREEMENT, Customer agrees to be bound by the terms of this Agreement, including the Documentation, Acceptable Use Policy, and represents that the person accepting this Agreement has the legal authority to bind Customer to this Agreement.
## 1. NOMADICML'S PROVISION OF SERVICES
### 1.1 Provision of Services
Nomadic AI will provide the Services in accordance with the terms and conditions of this Agreement and any applicable Order Form. The Services include our visual AI platform for video analysis, utilizing advanced machine learning models and computer vision technologies.
### 1.2 Security
Nomadic AI will implement and maintain industry-standard information security measures with administrative, physical, and technical safeguards designed to protect Customer Data. Customer acknowledges that Customer Data may be processed by Nomadic AI in the United States or in other countries in which Nomadic AI or its contractors operate.
### 1.3 Changes to Services
Nomadic AI may, at its discretion, update, modify, or enhance the Services from time to time. Nomadic AI will notify Customer in advance of changes that materially reduce core functionality.
## 2. CUSTOMER'S USE OF SERVICES
### 2.1 Customer Account Administration
Customer must maintain an Account to use the Services. Customer is responsible for:
* Designating administrators for its Account
* Maintaining updated contact information
* Managing access to administrator accounts
* Ensuring Authorized Users comply with this Agreement
### 2.2 Customer Responsibilities
Customer is responsible for any use of the Services through its Account, including all use by Authorized Users. Customer will:
* Maintain confidentiality of account credentials
* Prevent unauthorized use of the Services
* Not permit sharing of user accounts and passwords
* Ensure all uploaded content complies with applicable laws
### 2.3 Compliance
Customer may only use the Services in accordance with: (a) applicable laws and regulations, (b) this Agreement, (c) the Documentation, and (d) our Acceptable Use Policy. Customer will provide information necessary for Nomadic AI to verify compliance if requested.
### 2.4 Customer Materials
Customer represents and warrants that: (a) it has necessary rights to provide Customer Data to Nomadic AI, (b) use of Customer Data under this Agreement will not violate third party rights, and (c) Customer Data will not contain restricted information unless agreed in writing.
### 2.5 Use Restrictions
Customer will not (and will not allow any third party to):
* (a) reverse engineer, decompile, or attempt to discover source code of the Services
* (b) copy, modify, or create derivative works of the Services
* (c) sell, resell, or distribute the Services
* (d) use the Services to create competitive products or for benchmarking
* (e) remove proprietary notices
* (f) use the Services or Output for hazardous activities where failure could cause serious injury or death
* (g) violate export control laws or regulations
* (h) create, or direct others to create, successive or additional free trial accounts after a free trial expires in order to avoid paying Fees
### 2.6 Suspension
Nomadic AI may suspend access if: (a) Customer's use poses a security risk or may adversely affect the Services, (b) Customer breaches this Agreement, or (c) to comply with legal requirements. Nomadic AI will provide prompt notice when practicable.
## 3. INTELLECTUAL PROPERTY RIGHTS
### 3.1 Nomadic AI Technology
Except for rights expressly granted herein, Nomadic AI owns and reserves all rights in the Nomadic AI Technology, including the Services, algorithms, models, and any improvements or enhancements thereto. Customer receives only a limited license to use the Services during the Term.
### 3.2 Customer Intellectual Property
Customer owns the Output and Customer Applications. Customer grants Nomadic AI a worldwide, royalty-free license to use: (a) Customer Data to provide the Services and generate Output, and (b) Customer Data and Output to analyze, support, and improve Nomadic AI's products and services, including development of machine learning models and algorithms.
### 3.3 Nomadic AI Data
Nomadic AI may collect and create usage data, statistics, aggregated and anonymized data, and de-identified insights derived from Customer's use of the Services and processing of Customer Data ("Nomadic AI Data"). Nomadic AI may use Nomadic AI Data to: (a) provide, analyze, support, and improve products and services, and (b) create and distribute reports about our services. Nomadic AI will not identify Customer as a source without prior written approval.
### 3.4 Background Intellectual Property
Nomadic AI retains all rights in proprietary methodologies, tools, models, software, documentation, know-how, and inventions: (a) existing prior to this Agreement, (b) developed independently, or (c) developed in connection with the Services but not specific Output or derivatives of Customer Data.
### 3.5 Suggestions
Any feedback or suggestions regarding the Services become Nomadic AI's property without obligation or compensation to Customer.
### 3.6 Customer Marketing
Nomadic AI may use Customer's name and logo to: (a) identify Customer as a Nomadic AI customer, (b) produce case studies with Customer approval, and (c) create marketing materials.
## 4. FEES AND PAYMENT
### 4.1 Fees
Customer will pay the Fees described in the Order Form. All Fees are non-refundable except as expressly provided herein and not subject to set-off.
### 4.2 Invoicing & Payment
Payment terms are as specified in the Order Form. If not specified:
* Subscription fees are due monthly in advance
* Overage fees are due monthly in arrears
* Invoices are payable within 30 days
* Customer authorizes charging payment methods on file for amounts due
### 4.3 Disputes & Late Payments
Customer must dispute Fees within 60 days of invoice date. Past due amounts accrue interest at 1.5% per month. Nomadic AI may suspend Services for non-payment after 5 days notice.
### 4.4 Taxes
Customer is responsible for all taxes except those on Nomadic AI's net income. Nomadic AI will invoice applicable sales tax when required.
## 5. TERM AND TERMINATION
### 5.1 Agreement Term
This Agreement remains in effect for the Term specified in the Order Form, with automatic renewals unless either party provides notice per the Order Form terms.
### 5.2 Termination
Either party may terminate if: (a) the other materially breaches and fails to cure within 10 days of notice, or (b) the other becomes subject to insolvency proceedings. Nomadic AI may terminate to comply with law.
### 5.3 Effect of Termination
Upon termination: (a) fees become immediately due, (b) rights granted cease except as provided herein, (c) Customer Data remains available for retrieval for 30 days, and (d) sections that should survive will survive, including Sections 3, 4, 6-11.
## 6. CONFIDENTIALITY
### 6.1 Definition
"Confidential Information" means business or technical information disclosed under this Agreement that is marked confidential or would be considered confidential by a reasonable person. This Agreement and Order Forms are mutual Confidential Information. Nomadic AI Technology is Nomadic AI's Confidential Information. Customer Data and Output are Customer's Confidential Information.
### 6.2 Obligations
Each party will: (a) not use Confidential Information except as permitted herein, and (b) limit access to those who need it for permitted purposes under confidentiality obligations.
### 6.3 Exceptions
Obligations don't apply to information that: (a) becomes public through no fault, (b) was known without confidentiality obligation, (c) is received from third party without restriction, or (d) is independently developed.
### 6.4 Compelled Disclosure
Parties may disclose when legally required, with prior notice when permitted and reasonable assistance to contest disclosure.
## 7. WARRANTIES AND DISCLAIMERS
### 7.1 Mutual Warranties
Each party warrants it has authority to enter this Agreement.
### 7.2 Service Warranty
Nomadic AI warrants the Services will perform substantially per Documentation. Customer's sole remedy is correction or, if not feasible, termination and pro-rata refund.
### 7.3 DISCLAIMERS
TO THE FULLEST EXTENT PERMITTED BY LAW, EXCEPT AS EXPRESSLY PROVIDED, NOMADICML DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SERVICES AND OUTPUT ARE PROVIDED "AS IS". NOMADICML DOES NOT WARRANT THAT SERVICES OR OUTPUT ARE ACCURATE, COMPLETE, OR UNINTERRUPTED.
### 7.4 AI Output
Customer acknowledges that Output is generated by probabilistic AI systems. Nomadic AI makes no warranty regarding Output accuracy. Customer is solely responsible for evaluating and using Output.
### 7.5 Beta Services
Beta features are provided "as-is" without warranty or support and may change or be discontinued anytime.
## 8. INDEMNIFICATION
### 8.1 By Customer
Customer will indemnify Nomadic AI from third-party claims arising from: (a) Customer Data, (b) Customer's use of Services or Output, (c) Customer Applications, or (d) breach of this Agreement.
### 8.2 By Nomadic AI
Nomadic AI will indemnify Customer from third-party claims that Nomadic AI's technology infringes US patents or copyrights, except arising from: (a) Customer specifications or data, (b) modifications not by Nomadic AI, (c) combination with third-party products, or (d) use after notice of infringement.
### 8.3 Procedures
Indemnified party must promptly notify and grant control of defense. These are the exclusive remedies for third-party IP claims.
## 9. LIMITATION OF LIABILITY
### 9.1 Consequential Damages Waiver
EXCEPT FOR CUSTOMER'S BREACH OF SECTION 2.5 OR INDEMNIFICATION OBLIGATIONS, NEITHER PARTY IS LIABLE FOR INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, LOST PROFITS OR DATA, EVEN IF ADVISED OF POSSIBILITY.
### 9.2 Liability Cap
EXCEPT AS PROVIDED IN 9.1, EACH PARTY'S TOTAL LIABILITY SHALL NOT EXCEED FEES PAID OR PAYABLE IN THE 12 MONTHS PRECEDING THE CLAIM.
### 9.3 Decisions Based on Output
Customer is solely responsible for all decisions based on Output. Results from AI are probabilistic and should be evaluated for accuracy appropriate to use case.
## 10. DISPUTES
### 10.1 Governing Law
This Agreement is governed by California law, excluding conflicts provisions.
### 10.2 Jurisdiction
Disputes shall be resolved exclusively in San Francisco County, California courts.
## 11. GENERAL PROVISIONS
### 11.1 Entire Agreement
This Agreement and Order Forms constitute the entire agreement between parties and supersede prior agreements.
### 11.2 Agreement Modification
Nomadic AI may modify this Agreement with 30 days notice for material changes.
### 11.3 Assignment
Neither party may assign without consent, except to affiliates or acquirers of substantially all assets.
### 11.4 Export Compliance
Customer shall comply with all export laws and regulations.
### 11.5 Force Majeure
Neither party is liable for delays due to circumstances beyond reasonable control.
### 11.6 Support Terms
NomadicML will provide technical support via email at [support@nomadicml.com](mailto:support@nomadicml.com) and designated channels during business hours (10:00 AM - 6:00 PM Pacific Time, excluding holidays). Support includes:
* Response to inquiries within one business day
* Assistance with platform features and functionality
* Bug investigation and resolution efforts
* Documentation and training resources
Additional support levels may be specified in Order Forms.
# Privacy Policy
Source: https://docs.nomadicml.com/more/privacy-policy
## Introduction
This Privacy Policy outlines how NomadicML Inc. ("we", "us", "our", "Nomadic AI") collects, uses, maintains, and discloses information from users ("User", "you", "your") of the Nomadic AI Software ("Software"). By using the Software, you consent to the data practices described in this policy.
## Information Collection and Use
We are committed to protecting your privacy and handling your data in an open and transparent manner while complying with the Google API Services User Data Policy, including the Limited Use requirements. We do **NOT** request Restricted and Sensitive Scopes from Google. For more information, please visit [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy).
### Information we access from your Google/GitHub account:
* Name
* Email address
* Profile picture
### Information from your Google/GitHub account that we store:
* Email address
Personal information is stored for as long as your account in Software is active.
We collect this information in order to facilitate the specific functionalities of the Software, including:
* Creating and disambiguating your account using your email address.
* Enabling collaboration by allowing you to invite collaborators into workspaces or be invited into other's workspaces using your email address.
* Using the default profile picture on your Nomadic AI account.
If you sign up to receive our news, offers, events or other initiatives regarding our service or other services that may be of interest to you, we will collect your email address and access your name to provide you with our updates in line with any preferences you have told us about. You can unsubscribe from our updates at any time by emailing [support@nomadicml.com](mailto:support@nomadicml.com).
We do not share any information from your Google/GitHub accounts with any third parties.
We do not collect any non-personal information from sign-in service providers, such as Google and GitHub.
Non-Personal Information is collected to further enhance the Software's functionality and user experience. Your data is accessed and used strictly in accordance with the functionalities you engage within the Software.
## Third-Party Data Sharing and User Consent
In order to provide enhanced functionalities within the Nomadic AI Software, we share certain data with third-party services. Specifically, user-uploaded datasets and logs are stored in databases, and cached in-memory on AWS EC2 instances.
Importantly, when your pipelines or evaluations use third-party model providers, we share the execution data with those providers, and it is then handled according to their privacy policies, as if you perform the same call without or bypassing Nomadic AI.
Before sharing data with these third-party tools, we ensure to:
* Clearly describe the nature of data being shared and the purpose of such sharing within our privacy policy and user interface.
* Provide users with the option to opt-out of data sharing with third-party services at any time, ensuring users have full control over their data and its privacy.
* Thoroughly de-personalize the data to ensure no personal information is shared with the third parties.
We are committed to upholding the privacy and security of our users' data, in alignment with Google's privacy standards and requirements for apps that interact with third-party tools.
## Security and Data Storage
We prioritize the security of your information through robust data management and security protocols. Uploaded data is stored encrypted in our databases.
## Changes to This Privacy Policy
We may update this policy periodically to reflect changes in our practices or regulatory requirements. We encourage you to review this page regularly to stay informed about how we are protecting your information.
## Limited Use Disclosure
Our use and transfer of information received from Google APIs adhere to the Google API Services User Data Policy, including the Limited Use requirements. This Software's access, use, storage, and sharing of Google user data are strictly limited to the scope of functionality described in this privacy policy and with your explicit consent.
## Data Deletion Request
We will only retain your personal information for as long as we need it. We consider the nature, sensitivity and purpose we collected your data for when deciding when it's time to delete it. We will in some cases keep your data for longer periods of time than necessary for the original purpose we collected it for if we need to do this to meet our legal, accounting or regulatory requirements.
If you wish to have your data deleted from Nomadic AI, please follow these steps to submit a data deletion request:
1. **Email your request**: Send an email to [support@nomadicml.com](mailto:support@nomadicml.com) with the subject "Data Deletion Request". Include your full name and the email address associated with your Nomadic AI account.
2. **Verification**: Our support team will verify your identity and account ownership to protect your information.
3. **Confirmation and Processing**: Once verified, you will receive an acknowledgment email with a unique confirmation number and a link to check the status of your request.
4. **Status Updates and Completion**: Use the provided link and confirmation number to track the progress of your request. A final confirmation will be sent upon completion.
If you have any questions or concerns regarding the data privacy, please contact our support team at [support@nomadicml.com](mailto:support@nomadicml.com).
# Overview
Source: https://docs.nomadicml.com/overview
Start using Nomadic today
Step by step guide on using our tool.
See curated examples in different applications.
Find more details on our SDK.
# Analyzing Videos
Source: https://docs.nomadicml.com/sdk/analyzing-videos/overview
Overview of prompt-based analyze()
Run prompt-based analysis on one or more uploaded videos. Prompt analysis detects custom events using natural language prompts and defaults to **Thinking** mode.
```python theme={null}
analysis = client.analyze(
"video_id_1",
prompt="detect vehicles parked on the sidewalk",
)
```
Use prompt analysis for custom natural-language requirements.
# Prompt Analysis
Source: https://docs.nomadicml.com/sdk/analyzing-videos/prompt-analysis
Detect custom events using natural language prompts
Detect custom events in videos using natural language prompts. The default analyzer uses **Thinking** mode, which matches the router behavior in the web app. Use **Fast** mode when you want speed-preferring analysis.
```python title="Prompt examples" theme={null}
# Single-video prompt, defaults to Thinking
analysis = client.analyze(
"video_id_1",
prompt="detect vehicles parked on the sidewalk",
)
# Fast mode
analysis = client.analyze(
"video_id_1",
prompt="detect delivery vans double parked",
mode="fast",
)
# Request available telemetry directly in the prompt
analysis = client.analyze(
"video_id_1",
prompt="detect speeding events and include available speed, GPS, and timestamp evidence",
)
# Batch prompt: analyze multiple IDs at once
batch = client.analyze(
["video_id_1", "video_id_2"],
prompt="detect jaywalking near intersections",
)
# Batch prompt: analyze every video in a folder
batch = client.analyze(
folder="fleet_uploads",
prompt="detect jaywalking near intersections",
)
# Batch prompt: analyze an organization folder
org_batch = client.analyze(
folder="fleet_uploads",
scope="org",
prompt="detect jaywalking near intersections",
)
# Batch prompt: analyze a read-only demo/sample folder
sample_batch = client.analyze(
folder="Construction Samples",
scope="sample",
prompt="detect workers near active machinery",
)
# Open an SDK-native video/event viewer
client.visualize(analysis)
# Open a viewer from a batch result or an existing batch ID
client.visualize(batch)
client.visualize(batch, only_with_events=True) # Hide videos with zero events
client.visualize(batch["batch_metadata"]["batch_id"])
```
**Required Parameters:**
| Parameter | Type | Description |
| ------------------- | ---------------------- | -------------------------------------------------------------------------- |
| `id(s)` or `folder` | `str \| Sequence[str]` | Video ID(s) or folder name (use one, not both) |
| `prompt` | `str` | Event description or question to analyze (e.g., "detect green crosswalks") |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mode` | `str` | `"thinking"` | Either `"thinking"` or `"fast"` |
| `scope` | `'user' \| 'org' \| 'sample'` | `None` | Folder lookup scope. Use the same scope used for folder creation/upload; use `'sample'` for read-only demo/sample folders. Only applies when `folder` is provided. |
| `timeout` | `int` | `2400` | Analysis timeout in seconds |
| `wait` | `bool` | `True` | Wait for analysis to complete |
**Returns:** Dict with `video_id`, `analysis_id`, `mode`, `status`, `summary`, and `events`.
Batch prompt analysis returns `batch_metadata` with `batch_id`, `batch_viewer_url`, and `mode`, plus a `results` list of normalized per-video results.
Use `client.visualize(batch)` when you already have the SDK batch result. Add `only_with_events=True` to hide videos with zero detected events. Use `client.visualize(batch_id)` to hydrate and render a saved batch later.
Prompt analysis does not expose router overrides, model selection, overlay flags, reasoning traces, or Wizarding Trace artifacts. Put analysis requirements directly in the prompt.
# add_batch_metadata()
Source: https://docs.nomadicml.com/sdk/batch-results-metadata/add-batch-metadata
Add or update custom metadata for a batch analysis
Add or update custom metadata for a batch analysis. Metadata is stored as key-value pairs and can be used to track experiments, versions, or any custom information about your batch runs.
```python theme={null}
# Add metadata to a batch
client.add_batch_metadata(
"batch_id",
{
"experiment_id": "exp-001",
"version": 2,
"model": "Nomadic-VL-XLarge",
"notes": "Test run with new parameters"
}
)
# Update existing metadata (new keys will be merged, existing keys overwritten)
client.add_batch_metadata(
"batch_id",
{
"version": 3,
"status": "completed"
}
)
# Retrieve metadata later
batch_results = client.get_batch_analysis("batch_id")
metadata = batch_results["batch_metadata"]["metadata"]
print(f"Experiment: {metadata.get('experiment_id')}")
print(f"Version: {metadata.get('version')}")
```
**Required Parameters:**
| Parameter | Type | Description |
| ---------- | ---------------------------- | -------------------------------------------------------------- |
| `batch_id` | `str` | ID of the batch to update (required) |
| `metadata` | `Dict[str, Union[str, int]]` | Dictionary with string keys and string/int values (non-nested) |
**Returns:** Dict with success status and updated metadata:
* `success`: Boolean indicating if the operation succeeded
* `batch_id`: The batch identifier
* `metadata`: Complete metadata dictionary after merge
**Raises:**
* `ValidationError`: If metadata format is invalid (e.g., nested objects, non-string keys, invalid value types)
* `NomadicMLError`: If batch is not found or you don't have permission to modify it
Only the batch owner can add or update metadata. New metadata keys will be merged with existing metadata, with new values overwriting any existing keys with the same name.
Metadata values must be strings or integers only - nested objects, arrays, booleans, or null values are not supported.
# get_batch_analysis()
Source: https://docs.nomadicml.com/sdk/batch-results-metadata/get-batch-analysis
Retrieve analysis results for a completed batch
Retrieve analysis results for a completed batch. Optionally filter events by approval status (approved, rejected, pending, or invalid), or return event-level CSV.
```python theme={null}
# Get all results from a batch
batch_results = client.get_batch_analysis("batch_id")
# Filter for only approved events
approved_only = client.get_batch_analysis(
"batch_id",
filter="approved"
)
# Filter for multiple statuses
pending_and_rejected = client.get_batch_analysis(
"batch_id",
filter=["pending", "rejected"]
)
# Return event-level CSV instead of JSON
csv_text = client.get_batch_analysis("batch_id", as_csv=True)
# Filter + CSV (only approved events are included)
approved_csv = client.get_batch_analysis(
"batch_id",
filter="approved",
as_csv=True
)
# Save CSV to disk
with open("batch-results-approved.csv", "w", encoding="utf-8", newline="") as f:
f.write(approved_csv)
# Access batch metadata
print(batch_results["batch_metadata"]["batch_type"])
print(batch_results["batch_metadata"]["batch_viewer_url"])
# Iterate through video results
for result in batch_results["results"]:
print(f"Video: {result['video_id']}")
print(f"Events: {len(result['events'])}")
for event in result["events"]:
print(f" - {event.get('label', '')} at {event.get('t_start', '')}-{event.get('t_end', '')}")
```
**Required Parameters:**
| Parameter | Type | Description |
| ---------- | ----- | -------------------------------------- |
| `batch_id` | `str` | ID of the batch to retrieve (required) |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `filter` | `str \| List[str]` | `None` | Filter events by approval status. Valid values: `'approved'`, `'rejected'`, `'pending'`, `'invalid'`. If omitted, returns all events for all videos (including videos with zero events). If provided, only matching events are returned and videos with zero matching events are excluded. |
| `as_csv` | `bool` | `False` | If `True`, returns CSV text instead of JSON. CSV is event-level (one row per event) and does not emit placeholder rows for videos with zero events. |
**Returns (`as_csv=False`, default):** Dict with two keys:
* `batch_metadata`: Contains batch information
* `batch_id`: The batch identifier
* `batch_viewer_url`: URL to view batch results in the web UI
* `batch_type`: Internal batch category. Prompt-analysis batches may currently appear as legacy `"ask"`.
* `analysis_type`: Internal analysis label used by the backend
* `review_status`: Whether the batch analysis have been fully reviewed by someone
* `review_status_updated_at`: Time the batch analysis review status was updated, N/A if not reviewed yet.
* `metadata`: Dictionary of custom metadata key-value pairs (empty dict if no metadata exists)
* Configuration details (for prompt batches: `prompt`, `category`, etc.)
* `results`: List of per-video analysis dictionaries
* `video_id`: ID of the video
* `analysis_id`: ID of the analysis
* `mode`: Analysis mode used
* `status`: Analysis status
* `events`: List of detected events (filtered by approval status if specified)
* Additional fields depending on analysis type
**Returns (`as_csv=True`):** `str` (CSV text)
* Header row is always included.
* Rows are event-level (one row per event).
* Current CSV columns:
* `Query`
* `Video`
* `Approval Status`
* `Timestamp`
* `Category`
* `Label`
* `AI Analysis`
* `Severity`
* `Video ID`
* `Analysis ID`
* `Batch ID`
* `Batch Viewer URL`
* `Status`
* `Confidence`
* `Import Source URI`
* `Summary`
**Raises:**
* `NomadicMLError`: If batch is not completed or other API errors occur
* `ValidationError`: If filter contains invalid values
The batch must be completed before you can retrieve its results. If you need to check batch status first, use the batch viewer URL.
# Cloud Storage Uploads
Source: https://docs.nomadicml.com/sdk/cloud-storage
Securely upload videos from AWS, GCP, or Azure using direct cloud integration or signed URLs.
# Uploading From Cloud Storage
Use the Nomadic web portal to connect cloud buckets and import videos without leaving the browser. In the app, open **Profile → Cloud Integrations** to launch the guided workflow, upload credentials, optionally save the connection, and pick files to ingest. The sections below outline the IAM setup each modal expects before you start the import.
### Google Cloud Storage (Web UI)
The UI walks through the same steps outlined below. Use these instructions to generate the service account credentials the modal requests:
1. **Create a service account**
* In the Google Cloud Console, go to **IAM & Admin → Service accounts → Create**.
* Name the account (for example `nomadic-importer`) and finish the creation wizard.
2. **Grant the account read access to your bucket**
* Open **Cloud Storage → Browser**, select the bucket that holds your videos, and open the **Permissions** tab.
* Click **Grant access**, add the service-account email, and assign both **Storage Object Viewer** and **Storage Legacy Bucket Reader** roles so that hierarchical listings work.
3. **Create and download a JSON key**
* Return to the service account, choose **Manage keys → Add key → Create new key → JSON → Create**.
* Download the `.json` file and keep it secure—Google will not show it again.
4. **Upload the credentials in Nomadic**
* In **Profile → Cloud Integrations**, click **Add Google Cloud Storage** and upload the JSON key in Step 1 of the modal.
* Provide the bucket name and optional prefix in Step 2, then test the connection. You can choose to save the integration for future imports.
5. **Select files and import**
* After the connection succeeds, pick the videos you want to ingest. Nomadic will use the uploaded key once to read the selected files.
Saved integrations appear at the top of the Cloud Integrations tab so you can reuse them without re-uploading keys.
### Amazon S3 / S3-Compatible Storage (Web UI)
Follow these steps to supply the credentials that the S3 modal expects:
1. **Create a least-privilege IAM policy**
* In the AWS Console, open **IAM → Policies → Create policy → JSON**.
* Paste a policy that grants `s3:ListBucket` on your bucket and `s3:GetObject` on the objects you plan to ingest. Example:
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::your-bucket-name",
"Condition": { "StringLike": { "s3:prefix": ["optional/prefix/*", "optional/prefix"] } }
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::your-bucket-name/optional/prefix/*"
}
]
}
```
* Adjust the bucket ARN (and prefix if you only want to expose a folder). You can use AWS's visual editor if you prefer.
2. **Create an IAM user for Nomadic AI**
* Still in IAM, go to **Users → Create user** and enable **Access key - Programmatic access**.
* Attach the policy from Step 1 (or `AmazonS3ReadOnlyAccess` if you want full read-only coverage of the bucket).
3. **Store the access keys**
* After the user is created, download the `.csv` or copy the `Access key ID` and `Secret access key`. AWS will not show the secret again.
4. **Enter the credentials in Nomadic**
* In **Profile → Cloud Integrations**, choose **Add S3-Compatible Bucket**. Step 1 asks for the access key, secret key, optional session token, the region, and an optional custom endpoint URL.
* Step 2 prompts for the bucket name and an optional prefix. Enable "Save this integration" if you want to reuse it.
5. **Validate, pick files, and import**
* The modal tests the credentials and lists your objects. Select the videos you want, then continue to kick off the import flow.
Saved S3 integrations also appear in the Cloud Integrations tab, so future imports only require choosing the integration and prefix.
For Cloudflare R2 and other S3-compatible providers:
* Keep using `s3://bucket/key.mp4` URIs in SDK/API upload calls.
* Save the provider's custom endpoint in the S3 integration (for R2 this is typically `https://.r2.cloudflarestorage.com`).
* Use the provider's recommended region value. For Cloudflare R2 this is usually `auto`.
### Amazon S3 for MCAP Cloud Ingest
MCAP cloud ingest uses AWS IAM role federation and Google Storage Transfer
Service. This is separate from the access-key S3 integration used for normal
video imports: the backend starts a cloud-to-cloud transfer from your S3 bucket
into Nomadic storage, then processes the MCAP after the copied object is present.
1. **Fetch the Google identity to trust**
```python theme={null}
setup = client.cloud_integrations.get_s3_storage_transfer_setup()
print(setup["google_service_account_subject_id"])
print(setup["aws_trust_policy_template"])
```
2. **Create an IAM policy with read-only access to the MCAP prefix**
Replace `your-mcap-bucket` and `mcap/` with your bucket and prefix.
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::your-mcap-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["mcap/", "mcap/*"]
}
}
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-mcap-bucket/mcap/*"
}
]
}
```
The role does not need delete access.
3. **Create an IAM role trusted by Google Storage Transfer**
In AWS IAM, create a role with a custom trust policy. Use the exact
`google_service_account_subject_id` returned by
`get_s3_storage_transfer_setup()`.
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "accounts.google.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"accounts.google.com:sub": ""
}
}
}
]
}
```
Attach the read-only S3 policy from Step 2 to this role, then copy the role ARN.
4. **Save the Storage Transfer integration**
```python theme={null}
integration = client.cloud_integrations.add_s3_storage_transfer(
name="MCAP archive",
bucket="your-mcap-bucket",
prefix="mcap/",
role_arn="arn:aws:iam::123456789012:role/NomadicMcapTransferRole",
)
```
5. **Start an MCAP cloud ingest**
```python theme={null}
job = client.upload(
"s3://your-mcap-bucket/mcap/example-017-droid-ds.mcap",
folder="mcap_cloud_test",
integration_id=integration["id"],
wait_for_uploaded=False,
)
final = client.wait_for_mcap_import_job(
job["mcap_import_job_id"],
timeout=7200,
)
```
Use the role-based `s3_storage_transfer` integration for `.mcap` imports. The
access-key S3 integration above remains the correct setup for normal `.mp4`
cloud video imports.
### Hugging Face Buckets (Web UI)
Nomadic can also pull videos from Hugging Face Storage Buckets using a stored Hugging Face token.
1. **Create a Hugging Face token**
* In Hugging Face, open **Settings → Access Tokens**.
* Prefer a **fine-grained** token if Hugging Face exposes the bucket access you need.
* If bucket scoping is not available, use a dedicated Hugging Face account or storage-only token for Nomadic imports.
2. **Open the Hugging Face modal in Nomadic**
* Go to **Profile → Cloud Integrations → Add Hugging Face Bucket**.
* Enter the token, bucket id in `namespace/name` format, and an optional prefix.
3. **Validate and preview files**
* Nomadic tests the token against the bucket and lists readable video files.
* You can save the integration so later imports do not require re-entering the token.
4. **Import into a Nomadic folder**
* Select the files you want and continue to the normal upload/import modal.
* Imported videos are stored as standard Nomadic videos and can be analyzed like any other upload.
With either integration in place, the Profile page lets you reopen the modal at any time to browse your bucket and launch new imports straight from the web portal—no additional setup required.
# Livestreams
Source: https://docs.nomadicml.com/sdk/livestreams
Start livestream analysis sessions, listen for events, and inspect stream timing fields.
Use `client.livestream` to run continuous analysis on a live video source such as an HLS `.m3u8` stream. A session pulls the stream, chunks it, runs the rapid-review query on each chunk, and appends detected events to the session.
[](https://colab.research.google.com/drive/1zPgjWq3A_I_0JGLEMIkmr9mEOZ-n2f7F?usp=sharing)
## Start a Session
```python theme={null}
from getpass import getpass
import os
from nomadic import NomadicAI
api_key = os.environ.get("NOMADICAI_API_KEY") or getpass("Nomadic API key: ")
client = NomadicAI(api_key=api_key)
stream_url = "https://stream.nomadicml.com/stream2.m3u8"
result = client.livestream.start_session(
source_url=stream_url,
name="Robot pick demo",
rapid_review_query="detect robot picking up an apple",
)
stream_id = result["stream_id"]
session_id = result["session_id"]
print(stream_id, session_id)
print(f"https://app.nomadicml.com/events/{stream_id}/{session_id}")
```
`source_url` should point to a reachable live stream. HLS `.m3u8` URLs are the common path for browser-viewable livestreams.
**Parameters:**
| Parameter | Type | Default | Description |
| -------------------- | ------------- | ------- | ----------------------------------------------------------------- |
| `source_url` | `str` | — | Reachable live-stream URL. |
| `name` | `str` | — | Friendly session name shown in the web UI. |
| `rapid_review_query` | `str \| None` | `None` | Natural-language query for continuous event detection. |
| `stream_id` | `str \| None` | `None` | Existing parent stream ID. Omit it to let the backend create one. |
## Listen for Events
`iter_events()` polls the session and yields each new event once. Use `poll_interval` to control how often the SDK checks for new events and `timeout` to stop listening after a fixed number of seconds.
```python theme={null}
for event in client.livestream.iter_events(
stream_id,
session_id,
poll_interval=10,
timeout=180,
):
severity = event.get("severity", "info").upper()
event_type = event.get("type", "unknown")
stream_time = event.get("stream_time", "?")
description = event.get("description", "")
print(f"[{severity}] {event_type} @ {stream_time}s - {description}")
```
**Parameters:**
| Parameter | Type | Default | Description |
| --------------- | --------------- | ------- | ---------------------------------------------------- |
| `stream_id` | `str` | — | Parent stream ID returned by `start_session()`. |
| `session_id` | `str` | — | Session ID returned by `start_session()`. |
| `poll_interval` | `float` | `5.0` | Seconds between polling attempts. |
| `timeout` | `float \| None` | `None` | Maximum seconds to listen before stopping iteration. |
## End and Fetch the Final Session
```python theme={null}
client.livestream.end_session(stream_id=stream_id, session_id=session_id)
final = client.livestream.get_session(stream_id, session_id)
print(final["status"])
print(final["chunk_count"])
print(len(final.get("events", [])))
```
Completed sessions include the final chunk count and the accumulated event list.
`end_session(stream_id, session_id)` stops an active session. `get_session(stream_id, session_id)` returns the current or final session payload.
## Session Fields
| Field | Description |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| `stream_id` | Stable stream identifier. The backend can create one when omitted from `start_session()`. |
| `session_id` | Identifier for this run of the stream. Use it with `get_session()`, `iter_events()`, and `end_session()`. |
| `name` | Friendly session name shown in the web UI. |
| `source_url` | Original livestream URL supplied to `start_session()`. |
| `status` | Session lifecycle status such as `INITIALIZING`, `ACTIVE`, `FINISHED`, or `FAILED`. |
| `chunk_count` | Number of stream chunks processed so far. |
| `events` | Detected rapid-review events when `rapid_review_query` is supplied. |
| `chunks` | Chunk metadata for the session. |
## Event Timing Fields
Livestream event timestamps are relative to the session timeline.
| Field | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `stream_time` | Event start time in seconds from the beginning of the livestream session. This is the main field to display in user-facing logs. |
| `capture_time` | Capture time in seconds on the stream timeline. Often matches `stream_time`. |
| `chunk_index` | Zero-based index of the processed livestream chunk where the event was detected. |
| `chunk_relative_time` | Event time in seconds relative to the start of the chunk. |
| `hls_cumulative_offset` | Offset in seconds contributed by prior HLS chunks. |
| `t_start` / `t_end` | Human-readable event window inside the session, usually formatted as `MM:SS`. |
| `created_at` | ISO timestamp for when the event record was created by the backend. |
| `analysis_id` | Analysis run that produced the event. |
| `chunk_id` | Backend chunk identifier associated with the event. |
Example event shape:
```python theme={null}
{
"type": "Security",
"label": "robot picking up an apple",
"description": "robot picking up an apple",
"severity": "low",
"confidence": 0.95,
"stream_time": 9.0,
"chunk_relative_time": 9.0,
"t_start": "00:09",
"t_end": "00:16",
"created_at": "2026-03-07T22:51:55.312121+00:00",
"chunk_index": 0,
"chunk_id": "session_000",
"analysis_id": "session_analysis_000",
}
```
## Signed Playback Manifest
After chunks are available, `get_signed_manifest()` returns a short-lived signed HLS manifest URL for playback.
```python theme={null}
manifest = client.livestream.get_signed_manifest(session_id)
print(manifest["url"])
print(manifest["expires_at"])
```
Use the returned `url` with an HLS-capable player.
# LeRobot / GR00T Export
Source: https://docs.nomadicml.com/sdk/robotics-lerobot-export
Turn Raw Robot Footage into a Subtask-Annotated, Training-Ready `LeRobot` Dataset for GR00T Finetuning.
# Nomadic → LeRobot: Sub-Task Annotation for GR00T Fine-Tuning
Consider the steps required for a robot to complete a long-horizon task like "make me
a cup of coffee": grab the pod, insert it, place the cup, press the button, wait, pick
up the finished cup. If your training data labels the whole demonstration with that
single sentence, the policy never sees where one sub-skill ends and the next begins.
"Insert the pod" and "pick up the cup" get the exact same language conditioning as
everything in between. That flat labeling throws away the sub-task structure that makes
long-horizon tasks learnable, and it makes failures hard to diagnose since you can't
tell which sub-skill broke.
Nomadic's action segmentation finds where each sub-task starts and ends in raw footage
and gives each one its own language label, turning a flat "make me a cup of coffee"
trajectory into a structured, multi-phase episode a policy can actually learn from.
This guide walks through that pipeline end to end:
1. **Fit a segmentation model on your fleet data** to adapt sub-task boundaries to
your specific robot and task.
2. **Run automated sub-task annotation** on your videos to find sub-task boundaries
and language-label each one.
3. **Export to a LeRobot dataset** ready for GR00T fine-tuning, with each source
video as one episode and sub-tasks written as a per-frame `task_index` timeline
inside it.
4. **Finetune GR00T** on the exported dataset.
The exported format is LeRobot v2.1 (`meta/info.json`, `meta/tasks.jsonl`,
`meta/modality.json`, per-episode parquet + mp4), the format GR00T fine-tuning
expects.
Run this walkthrough end to end in [Google Colab](https://colab.research.google.com/drive/1SkHiteAQzESTtPtzgXKKteUZZhg-JvAO?usp=sharing).
## Setup
```python theme={null}
from nomadic import NomadicAI
from nomadic.video import AnalysisType
client = NomadicAI(api_key="your_api_key")
```
## 1. Fit a segmentation model on your fleet data
With Nomadic, you can easily train a sub-task segmentation model that uses your
robot's common motion patterns to learn its sub-tasks.
Train from a pre-computed trajectory NPZ (`external_data=`). Point this at your own
fleet's local `.npz` file — Nomadic uploads it to managed storage automatically.
**NPZ schema.** The file must contain these three arrays:
| Key | Shape / type | Description |
| ---------------- | -------------------------- | -------------------------------- |
| `signal` | `(n_channels, T)`, `float` | Per-channel trajectory signal |
| `t_sec` | `(T,)`, `float` | Timestamp per sample, in seconds |
| `sample_rate_hz` | `float` scalar | Sampling rate, e.g. `50.0` |
```python title="Train a segmentation model from your fleet trajectory NPZ" theme={null}
trajectory_npz = "./my_fleet_trajectories.npz"
# `domain="manipulation"` picks tuned defaults for arm-style motion.
# Use `domain="construction"` for heavy-machinery motion instead.
segmenter_job = client.train_segmenter(
name="my-manipulator-segmenter",
external_data=trajectory_npz,
domain="manipulation",
epochs=40, # optional; server picks a sensible default when unset
)
```
Poll until training completes:
```python theme={null}
import time
segmenter_status = client.get_segmenter_status(segmenter_job["job_id"])
while segmenter_status["status"] not in {"completed", "failed"}:
print("segmenter training:", segmenter_status["status"])
time.sleep(15)
segmenter_status = client.get_segmenter_status(segmenter_job["job_id"])
```
`get_segmenter_status` returns `status`, `segmenter_id` (populated once
`status == "completed"`), `created_at` / `completed_at`, and `error` on failure.
## 2. Run automated sub-task annotation
With the model trained, pass its ID into `analyze()` alongside
`AnalysisType.ACTION_SEGMENTATION`. Sub-task boundaries now come from the trajectory
phases the model learned.
```python title="Analyze videos with your trained segmentation model" theme={null}
if segmenter_status["status"] != "completed":
raise RuntimeError(f"Segmenter training failed: {segmenter_status.get('error')}")
segmenter_id = segmenter_status["segmenter_id"]
folder_videos = client.my_videos(folder="YOUR_FOLDER_NAME", scope="org")
videos = [v["video_id"] for v in folder_videos[:10]]
segmentation_result = client.analyze(
videos,
analysis_type=AnalysisType.ACTION_SEGMENTATION,
segmenter_id=segmenter_id,
)
batch_id = segmentation_result["batch_metadata"]["batch_id"]
```
`segmenter_id` is accepted by `client.analyze(...)` for single videos, lists of video
IDs, and folder/batch calls when `analysis_type=AnalysisType.ACTION_SEGMENTATION`.
### Review the segmented sub-tasks
Each event is a short manipulation sub-task with a natural-language label. Rather than
treating each span as its own clip, the export step below stitches these spans back
onto the full video as a per-frame sub-task timeline, giving VLAs like GR00T the
language conditioning signal they train on without breaking the continuous
demonstration apart.
```python theme={null}
client.visualize(segmentation_result, width=920)
batch_results = client.get_batch_analysis(batch_id)
for entry in batch_results["results"][:2]:
print(entry["video_id"], "-", len(entry.get("events", [])), "segments")
for event in entry.get("events", [])[:5]:
print(" ", event.get("t_start"), "-", event.get("t_end"), ":", event.get("label"))
```
## 3. Export to a LeRobot dataset
By default, each source video becomes one LeRobot episode, and each Nomadic-annotated
sub-task is included as a separate `task_index` inside that episode. If you prefer
the sub-tasks to be their own episodes, pass `episode_mode="per_segment"`.
```python title="Export a LeRobot v2.1 dataset for GR00T fine-tuning" theme={null}
export_result = client.export_lerobot_dataset(
batch_id=batch_id,
output_dir="./lerobot_dataset",
trajectory_tool="manipulator_trajectory",
camera_key="exterior",
robot_type="franka",
)
```
**Required parameters:**
| Parameter | Type | Description |
| ----------------------- | -------------- | ---------------------------------------------------------------------------------------------------------- |
| `batch_id` or `results` | `str` / `dict` | An action-segmentation batch ID, or an already-fetched `get_batch_analysis()` payload (mutually exclusive) |
| `output_dir` | `str` | Local directory to write the dataset into |
**Returns:** `output_dir`, `num_episodes`, `num_frames`, `num_tasks`, `tasks`, `fps`,
`state_dim`, `state_names`, `skipped_segments`, `warnings`.
**Requires:** `pip install 'nomadic[lerobot]'` (numpy, pandas, pyarrow) and the
`ffmpeg` / `ffprobe` binaries on `PATH`. Re-running against the same `output_dir`
wipes and regenerates it.
If `skipped_segments` isn't empty, those source videos didn't have a completed
`manipulator_trajectory` artifact to draw proprioception from. Point the pipeline at
your own manipulator footage with trajectory imported, or pass `trajectory_tool=None`
to export a video-only dataset (no `observation.state` / `action` features).
### Inspect the exported dataset
The layout matches what GR00T's fine-tuning pipeline expects: `meta/modality.json`
maps the flattened trajectory channels to named state/action groups, and each episode
is a `(parquet, mp4)` pair. `meta/episodes.jsonl`'s `tasks` list and the parquet's
`task_index` column are where the sub-task annotation actually lives. Check that
`task_index` changes over the course of the episode where you'd expect a sub-task
transition.
```python theme={null}
import json
from pathlib import Path
import pandas as pd
dataset_root = Path(export_result["output_dir"])
info = json.loads((dataset_root / "meta" / "info.json").read_text())
modality = json.loads((dataset_root / "meta" / "modality.json").read_text())
episode_df = pd.read_parquet(dataset_root / "data" / "chunk-000" / "episode_000000.parquet")
episode_df.head()
```
## 4. Finetune GR00T
The exported directory is ready to hand to NVIDIA's fine-tuning workflow:
1. Copy/mount `./lerobot_dataset` where your GR00T fine-tuning environment can read it.
2. Follow the [real-robot fine-tuning guide](https://docs.nvidia.com/learning/physical-ai/gr00t-e2e-workflow/latest/real-robot-workflow/real-fine-tuning-and-leapp.html).
The exported `meta/modality.json` is auto-generated in the same spirit as the
config defaults GR00T generates for its own data-collection pipeline; review
and adjust the per-group `absolute`/`rotation_type` settings for your robot
before training.
3. Launch `launch_finetune.py` pointed at `./lerobot_dataset`.
Happy fine-tuning!
# SDK Usage Examples
Source: https://docs.nomadicml.com/sdk/sdk-examples
Practical examples of using the Nomadic Python SDK for common tasks.
## Quick Start Notebook
For programmatic access to Nomadic, you can use our Python SDK. Quick Start Notebook below.
[](https://colab.research.google.com/drive/18J_Q-5wTS2xLjryqA-b2OtBetfCC9zMv)
### 1. Install the SDK
```bash theme={null}
pip install nomadic
```
### 2. Initialize the Client
```python theme={null}
from nomadic import NomadicAI
import os
# Initialize with your API key
client = NomadicAI(
api_key=os.environ.get("NOMADICAI_API_KEY")
)
```
To get your API key, log in to the web platform, go to Profile > API Key, and generate a new key.
We recommend storing your API key in an environment variable for security.
### 3. Upload and Analyze Videos
The standard workflow involves uploading your videos first, then running analysis on them.
Uploads accept local paths or remote URLs that end with a common video extension (`.mp4`, `.mov`, `.avi`, `.webm`):
```python theme={null}
response = client.upload('https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Mayhem-on-Road-Compilation.mp4')
# Extract video ID
video_id = response["video_id"]
# Add scope="org" when uploading to shared organization folders.
# Then analyze it
analysis = client.analyze(video_id, prompt="Find outlier events")
print(analysis)
```
You can also pass a list of paths/URLs to `upload` and a list of ids to `analyze` for batch operations.
```python theme={null}
paths = [
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Driving-a-bus-in-Switzerland-on-Snowy-Roads.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/LIDAR-RBG-Waymo-YouTube-Public-Sample.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Mayhem-on-Road-Compilation.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Oakland-to-SF-on-Bridge.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Zoox_San%20Francisco-Bike-To-Wherever-Day.mp4'
]
response = client.upload(paths)
video_ids = [v['video_id'] for v in response]
batch = client.analyze(video_ids, prompt="Find outlier events")
print(batch["batch_metadata"]) # Contains batch_id, batch_viewer_url, batch_type
for result in batch["results"]:
print(result["video_id"], result["analysis_id"], len(result.get("events", [])))
```
### 4. Semantic Search with Chain-of-Thought
Search can be used are open in the natural language queries. Nomadic will reason about what fits best. Search response includes a chain-of-thought summary plus the reasoning behind each matched video. Supply
the natural language query, the folder name, and the scope (`"user"`, `"org"`,
or `"sample"`), and the call returns the complete set of results in one
payload.
```python theme={null}
results = client.search(
query="Find near-misses with pedestrians on crosswalks",
folder_name="fleet_uploads_march",
scope="org", # optional, defaults to "user"
)
print(results["summary"]) # overall overview
print(results["thoughts"]) # list of reasoning steps
for match in results["matches"]:
print(match["video_id"], match["reason"], match["similarity"])
# Advanced: reuse the session ID if you want to reference the same results later
print(results["session_id"]) # Unique identifier for this search session
```
### 5. Analysis
Nomadic prompt analysis can be run on a single video, a list of videos, or a folder.
#### Prompt Analysis
Extracts custom events based on your specific requirements. The default analyzer uses Thinking mode. Use Fast mode when you want speed-preferring router behavior.
[](https://colab.research.google.com/drive/1-IXsag4dZhv4oT-6wy7mf4iRLD99AuhJ#scrollTo=Cofv1h7W1hur)
```python theme={null}
analysis = client.analyze("video_id_1", prompt="green crosswalk")
fast_analysis = client.analyze("video_id_1", prompt="green crosswalk", mode="fast")
print(analysis)
```
### 6. Project-Based File Management & Composite Workflows
For larger projects, you can organize videos into folders and run batch analysis on entire folders at once. This is especially useful for processing datasets or running systematic reviews.
#### Example: Deleting videos
```python theme={null}
client.delete_video(video_id)
# OR
for v in video_ids:
client.delete_video(v)
```
#### Example: Creating and looking up folders
```python theme={null}
# Create a new personal folder
folder = client.create_folder("marketing", description="Q1 campaign")
print(folder["id"], folder["created_at"])
# Lookup by name (personal scope by default)
existing = client.get_folder("marketing")
print(existing["id"], existing["video_count"])
# Organization-scoped folders
org_folder = client.create_folder("fleet_uploads", scope="org")
org_existing = client.get_folder("fleet_uploads", scope="org")
```
#### Example: Analysis + Search Workflow
This example demonstrates a common workflow: first, run a broad analysis to cast a wide net, then use search across analysis results to hone in on specific events, and finally, run a detailed analysis on the resulting subset of videos.
```python theme={null}
paths = [
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Driving-a-bus-in-Switzerland-on-Snowy-Roads.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/LIDAR-RBG-Waymo-YouTube-Public-Sample.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Mayhem-on-Road-Compilation.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Oakland-to-SF-on-Bridge.mp4',
'https://storage.googleapis.com/videolm-bc319.firebasestorage.app/example-videos/Zoox_San%20Francisco-Bike-To-Wherever-Day.mp4'
]
# Define a folder for the project
folder_name = "prompt-analysis-videos"
project_folder = client.create_folder(folder_name, scope="org")
# If you're reusing an existing folder, use:
# project_folder = client.get_folder(folder_name, scope="org")
print(f"Using folder '{project_folder['name']}' scoped to {project_folder['scope']} (id={project_folder['id']})")
print("📁 Step 1: Uploading videos to project folder...")
response = client.upload(paths, folder=folder_name, scope="org")
print(f"✅ Successfully uploaded {len(response)} videos to '{folder_name}' folder")
print("\n🔍 Step 2: Running broad prompt analysis on all videos...")
analyses = client.analyze(
folder=folder_name,
scope="org",
prompt="Find risky road-user interactions and traffic safety events",
)
print(f"✅ Completed prompt analysis on {len(analyses)} videos")
print("\n🔎 Step 3: Searching for pedestrian-related incidents...")
# Use natural-language search over the analysis results
search_results = client.search(
query="Find risky incidents involving pedestrians",
folder_name=folder_name,
scope="org",
)
matching_video_ids = list(set([match['video_id'] for match in search_results['matches']]))
print(f"✅ Found {len(matching_video_ids)} videos with pedestrian incidents")
print(f"\n🎯 Step 4: Re-analyzing {len(matching_video_ids)} videos for pedestrian fault analysis...")
analyses = client.analyze(
matching_video_ids,
prompt="Mark incidents involving pedestrians where pedestrians are at fault",
)
print(f"✅ Completed detailed analysis on {len(analyses)} videos")
for analysis in analyses:
if analysis['events']:
print(f"\n🎬 Events found in video {analysis['video_id']}:")
for e in analysis['events']:
print(f" • {e}")
print("-" * 80)
print("\n🧹 Step 5: Cleaning up - deleting project videos...")
for response in client.my_videos(folder_name):
result = client.delete_video(response['video_id'])
```
### 7. Re-analyzing Videos
You don't need to re-upload videos to run new analyses. You can efficiently query already uploaded videos using either their specific `video_id`s or by organizing them into folders.
#### Re-analyzing Specific Videos by ID
This is the most direct way to re-run analysis on a few specific videos. After you upload a video, the API returns a `video_id`. Store this ID to reference the video in future calls.
```python theme={null}
# Replace these strings with the video IDs returned by upload
pedestrian_analysis = client.analyze(
["video_id_1", "video_id_2"],
prompt="pedestrians close to vehicle",
)
print(f"Found {len(pedestrian_analysis)} videos with pedestrian interactions.")
```
#### Using Folders for Batch Re-analysis
For larger-scale projects, organizing videos into folders is the best practice. This allows you to run analysis on an entire dataset with a single command.
```python theme={null}
folder_name = "2024_urban_driving_set"
# Step 1: Upload and organize your videos into a folder (only needs to be done once)
client.upload(
['/path/to/city_drive_1.mp4', '/path/to/city_drive_2.mp4'],
folder=folder_name
)
# Step 2: Run an initial analysis to find all road signs and their MUTCD codes
print(f"\nRunning initial analysis for 'road signs & MUTCD codes' in folder '{folder_name}'...")
pedestrian_analysis = client.analyze(
folder=folder_name,
prompt="Find all road signs and note their corresponding MUTCD codes?",
)
print(f"Found {len(pedestrian_analysis)} videos with road signs.")
# Step 3: Later, run a different analysis on the same set of videos
print(f"\nRunning second analysis for 'potholes' in folder '{folder_name}'...")
pothole_analysis = client.analyze(
folder=folder_name,
prompt="potholes or major road cracks",
)
print(f"Found {len(pothole_analysis)} videos with potholes.")
# Read-only demo/sample folders can be analyzed by opting into sample scope
sample_analysis = client.analyze(
folder="Construction Samples",
scope="sample",
prompt="detect workers near active machinery",
)
```
### 8. Visualizing Results
Use the SDK visualizer to inspect detected events against the source video.
#### Creating a Video/Event Viewer
The visualizer returns standalone HTML and displays inline in notebooks when possible.
```python theme={null}
result = client.analyze(
"video_id_1",
prompt="green crosswalk",
)
html = client.visualize(result)
```
For batch analysis, pass either the batch result or a saved `batch_id`:
```python theme={null}
batch = client.analyze(
["video_id_1", "video_id_2"],
prompt="green crosswalk",
)
html = client.visualize(batch)
html = client.visualize(batch, only_with_events=True) # Hide videos with zero events
# Later, hydrate the batch and render it by ID.
html = client.visualize(batch["batch_metadata"]["batch_id"])
```
### 9. Livestream Analysis
Use `client.livestream` to start a live HLS session, run a continuous rapid-review query, and poll newly detected events.
[](https://colab.research.google.com/drive/1zPgjWq3A_I_0JGLEMIkmr9mEOZ-n2f7F?usp=sharing)
```python theme={null}
from getpass import getpass
import os
from nomadic import NomadicAI
client = NomadicAI(
api_key=os.environ.get("NOMADICAI_API_KEY") or getpass("Nomadic API key: ")
)
result = client.livestream.start_session(
source_url="https://stream.nomadicml.com/stream2.m3u8",
name="Robot pick demo",
rapid_review_query="detect robot picking up an apple",
)
stream_id = result["stream_id"]
session_id = result["session_id"]
for event in client.livestream.iter_events(
stream_id,
session_id,
poll_interval=10,
timeout=180,
):
print(event.get("stream_time"), event.get("description"))
client.livestream.end_session(stream_id=stream_id, session_id=session_id)
final = client.livestream.get_session(stream_id, session_id)
print(final["status"], final["chunk_count"], len(final.get("events", [])))
```
Livestream event timing fields include `stream_time`, `capture_time`, `chunk_relative_time`, `t_start`, `t_end`, and backend creation timestamp `created_at`. See [Livestreams](/sdk/livestreams) for the full event schema.
### 10. Working with Overlay Metadata
Nomadic can extract telemetry data from on-screen overlays in videos. This is useful for videos with embedded metadata like timestamps, GPS coordinates, speed, altitude, or custom telemetry values.
**Important:** Metadata describing overlay fields must be provided at upload time. During analysis, request the telemetry you need directly in the prompt.
#### Uploading Videos with Metadata
You can provide metadata files that describe the overlay fields in your videos. Metadata must be a properly formatted JSON file according to the [Metadata Ingestion Spec](https://docs.google.com/document/d/1Stz24u2rZ6EsOU0qZEI8oVsZRlg-qMD9xMDymj2enKA/edit?usp=sharing), and the `.json` file must share the same base filename as the video (for example, `drone_footage.mp4` pairs with `drone_footage.json`).
```python theme={null}
# Single video with metadata file (names must match)
result = client.upload(("dashcam_video.mp4", "dashcam_video.json"))
# Multiple videos with mixed metadata
uploads = client.upload([
("video1.mp4", "video1.json"), # Video with metadata
"video2.mp4", # Video without metadata
("video3.mp4", "video3.json"), # Another video with metadata
])
print(f"Uploaded {len(uploads)} videos")
for upload in uploads:
print(f"Video ID: {upload['video_id']}, Status: {upload['status']}")
```
#### Overlay-Aware Queries
For videos uploaded with metadata or visible overlays, request the telemetry you need in the prompt. The router selects the appropriate extraction path.
```python theme={null}
analysis = client.analyze(
video_id,
prompt="Find speed limit violations and include available timestamp and GPS evidence",
)
# Access extracted overlay data in events
for event in analysis["events"]:
print(f"Event: {event['label']} at {event['t_start']}-{event['t_end']}")
overlay_values = event.get("overlay", {})
for field, values in overlay_values.items():
start = values.get("start")
end = values.get("end")
print(f" {field}: {start} -> {end}")
```
#### Batch Analysis with Overlay Extraction
For batch processing of videos with overlays:
```python theme={null}
# Step 1: Upload batch of videos with metadata
videos_with_metadata = [
("fleet_cam_001.mp4", "fleet_cam_001.json"),
("fleet_cam_002.mp4", "fleet_cam_002.json"),
("fleet_cam_003.mp4", "fleet_cam_003.json"),
]
upload_results = client.upload(videos_with_metadata, folder="fleet_telemetry")
video_ids = [r['video_id'] for r in upload_results]
batch_analysis = client.analyze(
video_ids,
prompt="harsh braking events where speed drops rapidly; include available speed telemetry",
)
# Process results with overlay data
for result in batch_analysis["results"]:
video_id = result["video_id"]
for event in result["events"]:
# Speed (and other custom telemetry) is exposed through the overlay map
speed_overlay = event.get("overlay", {}).get("frame_speed")
if speed_overlay:
print(
f"Video {video_id}: Speed changed from "
f"{speed_overlay.get('start')} to {speed_overlay.get('end')}"
)
```
#### Metadata File Format
The metadata JSON file should describe the fields that appear as overlays in your video. For the complete metadata ingestion specification and detailed schema documentation, see the [Metadata Ingestion Spec](https://docs.google.com/document/d/1Stz24u2rZ6EsOU0qZEI8oVsZRlg-qMD9xMDymj2enKA/edit?usp=sharing).
Example metadata file:
```json theme={null}
{
"fields": [
{
"name": "speed",
"type": "number",
"unit": "mph",
"position": "top-left"
},
{
"name": "gps_lat",
"type": "number",
"unit": "degrees"
},
{
"name": "gps_lon",
"type": "number",
"unit": "degrees"
},
{
"name": "timestamp",
"type": "timestamp",
"format": "ISO8601"
},
{
"name": "altitude",
"type": "number",
"unit": "meters"
}
]
}
```
Metadata files must have the same base filename as their corresponding video file. For example, `dashcam_recording.mp4` should have metadata named `dashcam_recording.json`.
### 11. Storing Results in a Document Database
All SDK methods return serializable Python dictionaries, which can be easily processed and stored in any document database.
#### Example: Storing in MongoDB
```python theme={null}
from pymongo import MongoClient
# Assume 'analysis_results' is the list of dicts from a client.analyze() call
results_to_store = []
for analysis in analysis_results:
# ... (processing logic from previous examples) ...
results_to_store.append(processed_event)
# Connect to MongoDB and insert the documents
try:
db_client = MongoClient('mongodb://localhost:27017/')
db = db_client['nomadicml_results']
collection = db['driving_events']
if results_to_store:
collection.insert_many(results_to_store)
print("Successfully saved results to MongoDB.")
except Exception as e:
print(f"An error occurred with MongoDB: {e}")
```
#### Example: Storing in Supabase
Supabase provides a Postgres database with a Python client that's simple to use.
```python theme={null}
from supabase import create_client, Client
import os
# Assume 'analysis_results' is the list of dicts from a client.analyze() call
results_to_store = []
for analysis in analysis_results:
# ... (processing logic from previous examples) ...
# Ensure your dict keys match your Supabase table columns
processed_event_for_supabase = {
'source_video_id': video_id,
'event_type': event.get('type'),
'timestamp_sec': event.get('time'),
'description': event.get('description'),
'severity': event.get('severity'),
'dmv_rule': event.get('dmvRule'),
'raw_ai_analysis': event.get('aiAnalysis')
}
results_to_store.append(processed_event_for_supabase)
# Initialize Supabase client
try:
url: str = os.environ.get("SUPABASE_URL")
key: str = os.environ.get("SUPABASE_KEY")
supabase: Client = create_client(url, key)
# Insert data into your 'events' table
if results_to_store:
data, count = supabase.table('events').insert(results_to_store).execute()
print(f"Successfully saved {len(data[1])} results to Supabase.")
except Exception as e:
print(f"An error occurred with Supabase: {e}")
```
## Next Steps
A guide to integrate with common cloud storage providers.
A concise listing of all video-related SDK functions
# SDK Installation
Source: https://docs.nomadicml.com/sdk/sdk_installation
Install and configure the Nomadic SDK
# Installing the Nomadic SDK
This guide covers the installation and basic configuration of the Nomadic Python SDK.
## Prerequisites
* Python 3.8–3.11
* pip (Python package installer)
Ensure that you have the latest up to date version to avoid potential issues during installation.
## Installation Steps
### 1. Standard Installation
Install the SDK directly from [PyPI](https://pypi.org/project/nomadicml/):
```bash theme={null}
pip install nomadic
```
This is the recommended method for most users.
### 2. Obtain your API Key
Get your API key first. Log in to [app.nomadicml.com](https://app.nomadicml.com).
Go to **Profile → API Keys**, and click **Generate New Key**.
The full key is shown only once — copy it immediately.
### 3. Basic Configuration
Once you have the SDK installed and your API key, you can initialize the client:
```python theme={null}
import os
from nomadic import NomadicAI
client = NomadicAI(api_key=os.environ["NOMADICAI_API_KEY"])
# Or with custom configuration for self-hosted deployments
client = NomadicAI(
api_key=os.environ["NOMADICAI_API_KEY"],
base_url="https://custom-deployment.example.com", # Optional: defaults to https://api-prod.nomadicml.com/
timeout=60, # Optional: Custom timeout in seconds
collection_name="custom_collection" # Optional: Firestore collection name for your private instance — provided by your Nomadic admin
)
```
If you are running a self-hosted VPC deployment of Nomadic, set `collection_name` to the Firestore collection provided by your Nomadic admin. If you are using the standard cloud version of Nomadic, leave `collection_name` out.
### 4. Verifying Installation
To verify that everything is set up correctly:
```python theme={null}
import os
from nomadic import NomadicAI
client = NomadicAI(api_key=os.environ["NOMADICAI_API_KEY"])
try:
auth_info = client.verify_auth()
print("Authentication successful:", auth_info)
except Exception as e:
print("Authentication failed:", e)
```
## Troubleshooting
**`AuthenticationError: Invalid API key`**
If your API key is missing, incorrect, or expired, double-check that:
* You copied the full key (it's only shown once at generation)
* The key hasn't expired — check the expiry date in **Profile → API Keys**
* Your environment variable is set correctly: `echo $NOMADICAI_API_KEY`
For other authentication issues, see the [Authentication Guide](/advanced/authentication).
For package conflicts, try installing in a virtual environment or upgrading to the latest version with `pip install --upgrade nomadic`.
## Next Steps
Now that you have the SDK installed and configured, you can:
Deploy and manage Nomadic within your own Virtual Private Cloud.
Get started quickly with the Nomadic tool.
# Search
Source: https://docs.nomadicml.com/sdk/search
Semantic search across analysed events in a folder
### search()
Run semantic search across all analysed events inside a folder. You can use open-ended natural language queries.
```python theme={null}
results = client.search(
query="red pickup truck overtaking",
folder_name="my_fleet_uploads",
scope="org", # optional, defaults to "user"
)
print(results["summary"])
for thought in results["thoughts"]:
print("•", thought)
```
**Required Parameters:**
| Parameter | Type | Description |
| ------------- | ----- | ------------------------------------------- |
| `query` | `str` | Natural-language search query |
| `folder_name` | `str` | Human-friendly folder name to search within |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ----------------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `scope` | `'user' \| 'org' \| 'sample'` | `'user'` | Scope hint for folder resolution. Use `'org'` for organization folders and `'sample'` for demo/sample folders. |
**Returns:** Dict with:
* `summary`: string overview of the findings
* `thoughts`: list of reasoning steps (chain-of-thought) shown in the UI
* `matches`: list of `{video_id, analysis_id, event_index, similarity, reason}`
* `session_id`: identifier for the associated search session (useful for re-fetching or sharing)
# Structured Exports
Source: https://docs.nomadicml.com/sdk/structured-exports
ASAM OpenODD-compliant CSV exports
### generate\_structured\_odd()
Produce an ASAM OpenODD-compliant CSV describing the vehicle's operating domain.
```python title="Structured ODD export" theme={null}
from nomadic import NomadicAI, DEFAULT_STRUCTURED_ODD_COLUMNS
client = NomadicAI(api_key="your_api_key")
# Use the default schema or customise it before calling the export.
columns = [
{
"name": "timestamp",
"prompt": "Log the timestamp in ISO 8601 format (placeholder date 2024-01-01).",
"type": "YYYY-MM-DDTHH:MM:SSZ",
},
{
"name": "scenery.road.type",
"prompt": "The type of road the vehicle is on.",
"type": "categorical",
"literals": ["motorway", "rural", "urban_street", "parking_lot", "unpaved", "unknown"],
},
]
odd = client.generate_structured_odd(
video_id="VIDEO_ID",
columns=columns or DEFAULT_STRUCTURED_ODD_COLUMNS,
)
print(odd["csv"])
print(odd.get("share_url"))
```
**Required Parameters:**
| Parameter | Type | Description |
| ---------- | ----- | ------------------------------------------------------------------ |
| `video_id` | `str` | ID of the analysed video whose operating domain you want to export |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ------------------------------- | -------------------------------- | --------------------------------------------------------------------------------- |
| `columns` | `Sequence[StructuredOddColumn]` | `DEFAULT_STRUCTURED_ODD_COLUMNS` | Column definitions matching the UI schema (name, prompt, type, optional literals) |
| `timeout` | `int` | client default | Request timeout override in seconds |
**Returns:** Dict containing:
* `csv`: The generated CSV text.
* `columns`: The resolved column schema (after validation).
* `reasoning_trace_path`: Final Firestore path used for reasoning logs.
* `share_id` / `share_url`: Optional sharing metadata if the backend stored the export.
* `processing_time`: Time spent generating the export.
* `raw`: Full backend response payload for additional introspection.
# Cloud Imports (GCS / S3 / Cloudflare R2 / Hugging Face Buckets)
Source: https://docs.nomadicml.com/sdk/uploading-videos/cloud-imports
Import videos directly from cloud storage
#### upload() for cloud URIs
Provide full `gs://`, `s3://`, or `hf://buckets/...` URIs to import videos from cloud storage.
```python title="Cloud import examples" theme={null}
# Import from GCS
batch = client.upload([
"gs://drive-monitor/uploads/trip-042/front.mp4",
"gs://drive-monitor/uploads/trip-042/rear.mp4",
])
# Returns: {"import_job_id": "ij_xxx", "status": "importing"}
# Import from S3 with a specific integration
client.upload(
"s3://drive-monitor-archive/2024-09-01/front.mp4",
integration_id="aws-prod",
)
# Import from Cloudflare R2 after saving it as an S3-compatible integration
client.upload(
"s3://customer-r2/incoming/front.mp4",
integration_id="r2-prod",
)
# Import into a folder
client.upload(
["s3://my-bucket/videos/clip_001.mp4", "s3://my-bucket/videos/clip_002.mp4"],
folder="fleet_uploads",
scope="org",
)
# Import from a Hugging Face bucket
client.upload(
"hf://buckets/JohnnyMnenonic/test/incoming/front.mp4",
integration_id="hf-bucket-prod",
)
```
**Cloud-specific parameter:**
| Parameter | Type | Default | Description |
| ---------------- | ----- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `integration_id` | `str` | `None` | Saved cloud integration identifier to use for imports. When omitted, the backend resolves access from the bucket when the provider supports it. |
All other parameters (`folder`, `scope`, etc.) are shared with local uploads — see the [full parameter table](/sdk/uploading-videos/local-files-urls).
**Returns:** `{"import_job_id": "ij_xxx", "status": "importing"}`
Cloud imports accept `.mp4` objects referenced by full `gs://bucket/object.mp4`, `s3://bucket/object.mp4`, or `hf://buckets/namespace/name/object.mp4` URIs. Wildcard patterns are not supported—list each object explicitly. For Hugging Face buckets, when no `integration_id` is provided the backend first checks for a saved `hf_bucket` integration matching the bucket and otherwise falls back to public bucket access automatically.
For S3-compatible providers such as Cloudflare R2, keep using `s3://bucket/object.mp4` URIs. The provider-specific endpoint belongs on the saved cloud integration, not in the import URI itself.
Cloud import requests are acknowledged asynchronously. Even when a request later fails validation or authorization in background processing, the API still returns an `import_job_id`; those failures surface as `UPLOADING_FAILED` rows in `get_import_job_videos()`.
#### Managing Cloud Integrations
Use this helper to manage reusable GCS/S3 credentials for cloud imports. See [Cloud Storage Uploads](/sdk/cloud-storage) for instructions on creating service-account keys and web UI setup.
```python title="Cloud integrations helper" theme={null}
# List every integration visible to your user/org
client.cloud_integrations.list()
# Filter by provider
client.cloud_integrations.list(type="gcs")
# Add a new GCS integration using a service account JSON file
client.cloud_integrations.add(
type="gcs",
name="Fleet bucket",
bucket="drive-monitor",
prefix="uploads/",
credentials="service-account.json", # path or dict/bytes
)
# Add a new S3 integration
client.cloud_integrations.add(
type="s3",
name="AWS archive",
bucket="drive-archive",
prefix="raw/",
region="us-east-1",
credentials={
"accessKeyId": "...",
"secretAccessKey": "...",
"sessionToken": "...", # optional
},
)
# Add a Cloudflare R2 integration using the same S3 helper
client.cloud_integrations.add(
type="s3",
name="Customer R2",
bucket="customer-r2",
prefix="incoming/",
endpoint_url="https://.r2.cloudflarestorage.com",
region="auto",
credentials={
"accessKeyId": "...",
"secretAccessKey": "...",
},
)
# Import from R2 using a normal s3:// URI and the saved integration id
client.upload(
"s3://customer-r2/incoming/front.mp4",
integration_id="r2-prod",
)
```
For Cloudflare R2 and other S3-compatible providers, keep using `s3://bucket/key.mp4`
URIs in `client.upload(...)`. The custom endpoint stays on the saved integration
via `endpoint_url`; it does not go into the import URI.
#### Multi-view (cloud)
Cloud multi-view uploads use dict mappings with `gs://` or `s3://` URIs. `front` is required in every set.
```python theme={null}
# Single cloud multi-view set
multi = client.upload(
{
"front": "s3://drive-monitor/uploads/trip-042/front.mp4",
"left": "s3://drive-monitor/uploads/trip-042/left.mp4",
"right": "s3://drive-monitor/uploads/trip-042/right.mp4",
},
folder="fleet_uploads",
scope="org",
)
# Returns: {"import_job_id": "ij_xxx", "status": "importing"}
```
* Multiple cloud multi-view sets submitted in one `upload([...])` call produce one import job.
* `wait_for_uploaded=True` is ignored for cloud multi-view uploads.
* For multi-view import jobs, `client.get_import_job_videos()` returns front rows only and `total` is the requested set count.
#### get\_import\_job()
Fetch metadata for a cloud import job.
```python theme={null}
upload = client.upload(
[
"s3://drive-monitor-archive/2024-09-01/front.mp4",
"s3://drive-monitor-archive/2024-09-01/rear.mp4",
],
wait_for_uploaded=False,
)
job = client.get_import_job(upload["import_job_id"])
print(job["job_id"], job["total"])
```
**Required Parameters:**
| Parameter | Type | Description |
| --------------- | ----- | ------------------------------------------ |
| `import_job_id` | `str` | Cloud import job ID returned by `upload()` |
**Returns:** Dict with job metadata fields such as:
* `job_id`
* `source` (`"s3"`, `"gcs"`, or `"hf_bucket"`)
* `bucket`
* `prefix`
* `folder_id`
* `folder_name`
* `total`
* timestamps (`created_at`, `completed_at`, `updated_at` when available)
#### Hugging Face integrations via the SDK
If you want to create and reuse a saved Hugging Face integration from the SDK,
create it with the cloud integrations helper and then pass its
`integration_id` into `upload()`:
```python theme={null}
integration = client.cloud_integrations.add_hf_bucket(
name="HF footage",
bucket="JohnnyMnenonic/test",
token="hf_xxx",
prefix="incoming/",
)
result = client.upload(
"hf://buckets/JohnnyMnenonic/test/incoming/front.mp4",
integration_id=integration["id"],
wait_for_uploaded=False,
)
```
Prefer a fine-grained token if Hugging Face supports the required bucket
access. If bucket-specific scoping is unavailable, use a dedicated token or
account reserved for storage imports.
Outside of integration creation, the SDK does not accept Hugging Face tokens.
Use `client.upload("hf://buckets/...")` with an `integration_id`, or omit
`integration_id` and let the backend resolve a saved integration or public
access for that bucket.
For high-volume imports, treat `total` + `client.get_import_job_videos()` as the readiness contract.
#### get\_import\_job\_videos()
Fetch paginated per-video upload statuses for a cloud import job.
This endpoint is designed for large import jobs where returning all rows in one
response would be expensive (for example, thousands to hundreds of thousands of
videos). For jobs with more than \~1,000 videos, prefer cursor-based pagination
(`limit` + `cursor`) instead of requesting everything at once.
```python theme={null}
upload = client.upload(
[
"gs://drive-monitor/uploads/trip-042/front.mp4",
"gs://drive-monitor/uploads/trip-042/rear.mp4",
],
wait_for_uploaded=False,
)
cursor = None # First page: no cursor
while True:
result = client.get_import_job_videos(
upload["import_job_id"],
limit=500,
cursor=cursor, # Pass cursor from previous response
)
print(result["import_job_id"])
print(result["video_count"], result["has_more"], result["next_cursor"]) # page_size, more pages?, next anchor
print(result["videos"][:3]) # [{video_id, status, import_source_uri}, ...]
if not result["has_more"]:
break
cursor = result["next_cursor"] # Use this value for the next page
```
**Required Parameters:**
| Parameter | Type | Description |
| --------------- | ----- | ------------------------------------------ |
| `import_job_id` | `str` | Cloud import job ID returned by `upload()` |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ----- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `limit` | `int` | `500` | Max videos to return in one page |
| `cursor` | `str` | `None` | Last `video_id` from previous page |
| `offset` | `int` | `None` | Offset-based fallback pagination (do not combine with `cursor`). Not supported for multi-view import jobs. |
**Returns:** Dict with:
* `import_job_id`
* `total` (requested count for this job; for multi-view jobs this is the requested set count)
* `limit`
* `cursor`
* `has_more`
* `next_cursor`
* `videos` (list of `{video_id, status, import_source_uri}` entries for detailed polling; multi-view jobs return front rows only)
* `video_count`
**Cursor Notes:**
* `cursor` is the last `video_id` from the previous response's `next_cursor`.
* Start with `cursor=None` for the first page.
* Stop paging when `has_more` is `False`.
* Prefer `cursor` pagination for large jobs; `offset` is mainly a fallback/debug option.
* Do not pass both `cursor` and `offset` in the same request.
* For multi-view cloud imports, `videos` contains currently materialized IDs (best-effort); use `total` as the requested target count.
`client.get_import_job_uploaded_video_ids()` remains available as a deprecated alias of `client.get_import_job_videos()`.
#### End-to-end cloud import example
```python theme={null}
import time
# 1. Start the cloud import — returns an import_job_id, NOT a video_id
result = client.upload([
"s3://my-bucket/videos/clip_001.mp4",
"s3://my-bucket/videos/clip_002.mp4",
"s3://my-bucket/videos/clip_003.mp4",
])
print(result)
# {"import_job_id": "ij_a1b2c3d4e5f6", "status": "importing"}
job_id = result["import_job_id"]
# 2. Poll import-job videos until returned IDs reach the requested total
# and each returned row is terminal.
TERMINAL = {"UPLOADED", "UPLOADING_FAILED"}
seen = {}
while True:
job = client.get_import_job(job_id)
target_total = int(job.get("total") or 0)
cursor = None
while True:
page = client.get_import_job_videos(job_id, limit=500, cursor=cursor)
for row in page["videos"]:
seen[row["video_id"]] = row["status"]
if not page["has_more"]:
break
cursor = page["next_cursor"]
if target_total > 0:
all_ids_materialized = len(seen) >= target_total
all_terminal = all(status in TERMINAL for status in seen.values())
print(f"Import progress: ids={len(seen)}/{target_total}")
if all_ids_materialized and all_terminal:
break
time.sleep(10)
# 3. Use uploaded IDs for analysis
video_ids = [vid for vid, status in seen.items() if status == "UPLOADED"]
print(f"Imported {len(video_ids)} videos: {video_ids[:5]}...")
# 4. Now use the video IDs for analysis
client.analyze(
video_ids,
prompt="detect lane departure events",
)
```
# Local File & URL Uploads
Source: https://docs.nomadicml.com/sdk/uploading-videos/local-files-urls
Upload local files, URLs, MCAPs, and multi-view sets
Upload local files or HTTP/HTTPS URLs. Returns a `video_id` per video.
```python title="Local file & URL upload examples" theme={null}
# Single local file
result = client.upload("video.mp4")
# Single video with custom display name
result = client.upload("video.mp4", name="Morning Commute.mp4")
# Single video with metadata
result = client.upload(("video.mp4", "video.json"))
# Multiple local files
batch = client.upload(["a.mp4", "b.mp4"])
# Multiple videos with mixed metadata
batch = client.upload([
("video1.mp4", "video1.json"), # Video with metadata
"video2.mp4", # Video without metadata
("video3.mp4", "video3.json") # Another video with metadata
])
# Batch upload with per-video custom names (dict syntax)
batch = client.upload([
{"video": "dashcam_001.mp4", "name": "Trip to Downtown"},
{"video": "dashcam_002.mp4", "name": "Highway Merge"},
{"video": "dashcam_003.mp4", "name": "Parking Lot Exit", "metadata": "trip3.json"}
])
# Public GCS URL (any accessible HTTPS URL)
remote = client.upload("https://storage.googleapis.com/my-bucket/videos/demo.mp4")
# URL with custom display name
remote = client.upload(
"https://storage.googleapis.com/my-bucket/videos/scene-1.mp4",
name="scene_1.mp4"
)
# With folder organization
result = client.upload("video.mp4", folder="my_folder")
# With metadata JSON file and folder
result = client.upload(("dashcam.mp4", "dashcam.json"), folder="fleet_videos")
# Organization scope
result = client.upload("launch.mp4", folder="robotics_org", scope="org")
```
#### Local MCAP Uploads
Upload local `.mcap` files through the same `upload()` helper. The initial response
contains an `mcap_ingest_id`; wait for completion to retrieve the derived videos.
```python title="Local MCAP upload" theme={null}
queued = client.upload("sample.mcap")
final = client.wait_for_mcap_ingest(queued["mcap_ingest_id"])
print("video_ids:", final["video_ids"])
print("videos_by_channel:", final.get("videos_by_channel", {}))
```
#### S3 MCAP Cloud Ingest
For large MCAPs already stored in S3, create a role-based S3 Storage Transfer
integration, then pass the `s3://...mcap` URI to `upload()`. This avoids routing
the source MCAP through your local machine or the Nomadic backend before storage
transfer starts.
```python title="S3 MCAP cloud ingest" theme={null}
BUCKET = "your-mcap-bucket"
PREFIX = "mcap/"
ROLE_ARN = "arn:aws:iam::123456789012:role/NomadicMcapTransferRole"
integration = client.cloud_integrations.add_s3_storage_transfer(
name="MCAP archive",
bucket=BUCKET,
prefix=PREFIX,
role_arn=ROLE_ARN,
)
job = client.upload(
f"s3://{BUCKET}/{PREFIX}example-017-droid-ds.mcap",
folder="mcap_cloud_test",
integration_id=integration["id"],
wait_for_uploaded=False,
)
final = client.wait_for_mcap_import_job(
job["mcap_import_job_id"],
timeout=7200,
)
print(final["status"])
print(final["video_ids"])
```
See [Cloud Storage Uploads](/sdk/cloud-storage) for the AWS IAM role
setup required before creating the S3 Storage Transfer integration.
#### Multi-view (local/URL)
Use a dict mapping view names to local files or URLs. `front` is required in every set.
```python theme={null}
# Single multi-view set
multi = client.upload(
{
"front": "https://example.com/trip-042/front.mp4",
"left": "https://example.com/trip-042/left.mp4",
"right": "https://example.com/trip-042/right.mp4",
},
folder="fleet_uploads",
scope="org",
)
# Multiple multi-view sets in one call
multi_batch = client.upload([
{
"front": "https://example.com/set1/front.mp4",
"left": "https://example.com/set1/left.mp4",
"right": "https://example.com/set1/right.mp4",
},
{
"front": "https://example.com/set2/front.mp4",
"left": "https://example.com/set2/left.mp4",
"right": "https://example.com/set2/right.mp4",
},
])
```
Local/HTTP multi-view uploads return the stitched front `video_id`.
* **Metadata sidecars** must share the same base filename as the video (e.g., `launch.mp4` + `launch.json`). See the [Metadata Ingestion Spec](https://docs.google.com/document/d/1Stz24u2rZ6EsOU0qZEI8oVsZRlg-qMD9xMDymj2enKA/edit?usp=sharing) for the full schema.
* **Custom names** (`name` param) are supported for single files, URLs, and batch dict syntax — not for cloud imports or multi-view.
* **Folders** are auto-created if they don't exist. Defaults to personal scope; use `scope="org"` for shared org folders.
**Required Parameters:**
| Parameter | Type | Description |
| --------- | ---------------------------------- | --------------------------------------------------------------------- |
| `videos` | `str \| Path \| tuple \| Sequence` | Single video, (video, metadata) tuple, or list of mixed videos/tuples |
**Optional Parameters:**
| Parameter | Type | Default | Description |
| ------------------- | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `str` | `None` | Custom display name for the uploaded video. Overrides the original filename. Only supported for single file uploads, URLs, or per-video in batch dict syntax. Not supported for cloud imports or multi-view uploads. |
| `folder` | `str` | `None` | Folder name for organizing uploads (unique within each scope) |
| `metadata_file` | `str \| Path` | `None` | Overlay metadata JSON file (must share the video's base filename) per [spec](https://docs.google.com/document/d/1Stz24u2rZ6EsOU0qZEI8oVsZRlg-qMD9xMDymj2enKA/edit?usp=sharing) (ignored when using tuples) |
| `scope` | `'user' \| 'org'` | `'user'` | Scope hint for folder resolution. Use `'org'` for shared org folders and `'user'` for personal uploads. |
| `upload_timeout` | `int` | `1200` | Timeout in seconds for upload completion |
| `wait_for_uploaded` | `bool` | `True` | Wait until upload is complete |
| `integration_id` | `str` | `None` | Saved cloud integration identifier for `gs://`, `s3://`, `hf://buckets/...`, and S3 MCAP imports. |
| `chunk_size` | `int` | `None` | Optional chunk size for MCAP ingest. |
| `front_channel` | `str` | `None` | Front-camera channel name for local MCAP stitching. |
| `channel_roles` | `Mapping[str, str]` | `None` | Optional channel-to-role mapping for local MCAP uploads. |
| `channel_labels` | `Mapping[str, str]` | `None` | Optional channel display labels for local MCAP uploads. |
**Returns:** Dict (single) or List\[Dict] (multiple) with `{"video_id": "...", "status": "processing" | "uploaded" | ...}`
# Uploading Videos
Source: https://docs.nomadicml.com/sdk/uploading-videos/overview
Overview of the upload() method and supported sources
The `upload()` method handles all video ingestion. The return type differs depending on the source:
* **Local video files & URLs** return a `video_id`.
* **Local `.mcap` files** return an `mcap_ingest_id` — use `client.wait_for_mcap_ingest()` or `client.get_mcap_ingest()` to retrieve derived `video_ids`.
* **Cloud video imports (`gs://`, `s3://`, `hf://buckets/...`)** return an `import_job_id` — use `client.get_import_job()` and `client.get_import_job_videos()` to retrieve video IDs.
* **S3 `.mcap` cloud imports** return an `mcap_import_job_id` — use `client.wait_for_mcap_import_job()` or `client.get_mcap_import_job()` to retrieve child ingests and derived `video_ids`.
# delete_video()
Source: https://docs.nomadicml.com/sdk/video-folder-management/delete-video
Remove a video by ID
Remove a video by ID.
```python theme={null}
client.delete_video("video_id")
```
**Parameters:**
| Parameter | Type | Description |
| ---------- | ----- | ------------------------------------ |
| `video_id` | `str` | ID of the video to delete (required) |
**Returns:** Dict with deletion status
# Folders
Source: https://docs.nomadicml.com/sdk/video-folder-management/folders
Create and look up folders
#### create\_folder()
Create a new folder in a specific scope. Raises an error if a folder with the
same name already exists in the target scope.
```python theme={null}
marketing = client.create_folder("marketing", description="Q1 campaign")
```
**Parameters:**
| Parameter | Type | Default | Description |
| ------------- | ----------------- | -------- | --------------------------- |
| `name` | `str` | — | Folder name to create |
| `scope` | `'user' \| 'org'` | `'user'` | Target scope for creation |
| `description` | `str \| None` | `None` | Optional folder description |
**Returns:** Dict with folder `id`, `name`, `org_id`, `created_at`, and `description`
#### get\_folder()
Lookup a folder by name. Defaults to your personal scope; pass `scope="org"`
for organization folders.
```python theme={null}
folder = client.get_folder("fleet_uploads")
org_folder = client.get_folder("fleet_uploads", scope="org")
```
**Parameters:**
| Parameter | Type | Default | Description |
| --------- | ----------------- | -------- | ---------------------- |
| `name` | `str` | — | Folder name to look up |
| `scope` | `'user' \| 'org'` | `'user'` | Scope to search within |
**Returns:** Dict with folder `id`, `name`, `org_id`, `scope`, `created_at`, `created_by`, `description`, and `video_count`
# my_videos()
Source: https://docs.nomadicml.com/sdk/video-folder-management/my-videos
Retrieve uploaded videos, optionally filtered by folder
```python theme={null}
# Get all videos
videos = client.my_videos()
# Get videos in specific folder
videos = client.my_videos(folder="my_folder")
# Get videos from a personal folder (when org folder has same name)
videos = client.my_videos(folder="shared_folder", scope="user")
# Get videos from an organization folder
videos = client.my_videos(folder="shared_folder", scope="org")
# Get videos from a read-only demo/sample folder
videos = client.my_videos(folder="Construction Samples", scope="sample")
```
**Optional Parameters:**
| Parameter | Type | Default | Description |
| --------- | ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `folder` | `str` | `None` | Filter videos by folder name |
| `scope` | `'user' \| 'org' \| 'sample'` | `None` | Disambiguate folder lookup when personal and org folders share the same name. `'user'` matches only personal folders, `'org'` matches only organization folders, and `'sample'` matches read-only demo/sample folders. When `None`, personal folders are preferred. |
**Returns:** `List[Dict]` - Each dict contains:
| Field | Type | Description |
| ------------- | ------- | -------------------------------------- |
| `video_id` | `str` | Unique video identifier |
| `video_name` | `str` | Original filename |
| `duration_s` | `float` | Video duration in seconds |
| `folder_id` | `str` | Folder identifier |
| `status` | `str` | Upload status (see below) |
| `folder_name` | `str` | Folder name (when filtering by folder) |
| `org_id` | `str` | Organization ID (if org-scoped) |
**Upload status values:**
| Status | Meaning |
| ------------------ | ------------------ |
| `processing` | Upload in progress |
| `uploading_failed` | Upload failed |
| `uploaded` | Ready for analysis |