# 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 Camera Placement ### 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). Nomadic login screen

Here you can test one of our sample queries, filter to different verticals, or upload your own videos.

Nomadic login screen
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. Nomadic login screen

The results page shows further details on the analysis. See the table below for the various menu options in the interface.

Nomadic login screen
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.

Nomadic login screen * 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. [![Open the livestream demo in Colab](https://colab.research.google.com/assets/colab-badge.svg)](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. [![Open this demo in colab](https://colab.research.google.com/assets/colab-badge.svg)](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. [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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. [![Open the livestream demo in Colab](https://colab.research.google.com/assets/colab-badge.svg)](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. Nomadic API Keys screen ### 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 |