# Running Assignments via API

## Overview

The Duvo Public API enables programmatic access to run Assignments from your own applications, scripts, or automation pipelines. Instead of triggering runs through the Duvo web interface, you can integrate Assignments directly into your workflows.

### Key Concepts

**Assignments**: Assignments are configured through the Duvo web interface with a specific SOP, connections, and capabilities. The Public API allows you to start Jobs for them programmatically. To create and manage Assignments via API, see [Creating Assignments via API](https://docs.duvo.ai/running-assignments/creating-assignments-via-api).

**Builds**: A Build is a versioned Setup snapshot for an Assignment. Every time an Assignment is published, a new Build is created. Runs always execute against an Assignment's latest Build. You can also create Builds via the API to deploy Setup changes programmatically.

**Runs**: A run represents a single execution of an Assignment. When you start a run, the Assignment begins working on its configured task. Runs are asynchronous—the API returns immediately with run information, and you poll for status updates.

**Sandboxes**: Sandboxes are isolated file environments where you can upload files before starting a run. If your Assignment needs to process files (CSVs, documents, images, etc.), you first upload them to a sandbox, then reference that sandbox when starting the run.

**Human-in-the-Loop (HITL)**: Some Assignments may require human input during execution. When this happens, the run enters a "waiting" status. You can provide a webhook URL when starting a run to receive notifications when human input is needed, then respond via the API.

### Base URL

All API endpoints are available at:

```
https://api.duvo.ai/v1
```

### Rate Limiting

The API enforces rate limits per API key (300 requests per minute). Rate limit information is included in response headers:

| Header                  | Description                                         |
| ----------------------- | --------------------------------------------------- |
| `x-ratelimit-limit`     | Maximum requests allowed in the window              |
| `x-ratelimit-remaining` | Requests remaining in current window                |
| `x-ratelimit-reset`     | Unix timestamp when the window resets               |
| `retry-after`           | Seconds to wait before retrying (when rate limited) |

When rate limited, the API returns a `429 Too Many Requests` response.

***

## Authentication

All Public API requests require authentication using an API key. API keys are scoped to a team and inherit the permissions of the user who created them.

### API Key Format

API keys follow the format `dv_<random>` (e.g., `dv_abc123xyz789...`). They are approximately 35 characters long.

### Using Your API Key

Include your API key in the `Authorization` header as a Bearer token:

```bash
curl -X GET "https://api.duvo.ai/v1/health" \
  -H "Authorization: Bearer dv_your_api_key_here"
```

### Creating API Keys in the UI

1. Navigate to **Team Settings** → **API Keys** in the Duvo web interface
2. Click **Create API Key**
3. Enter a descriptive name for the key (e.g., "Production Pipeline", "CI/CD Integration")
4. Click **Create**
5. **Copy the key immediately** — it will only be shown once and cannot be retrieved later

> ⚠️ **Important**: Store your API key securely. Treat it like a password. If you lose it, you'll need to create a new one.

### Managing API Keys

From the API Keys settings page, you can:

* **View all keys** for your team, including who created them and when they were last used
* **Delete keys** that are no longer needed or may have been compromised

Only team Admins and Owners can create and manage API keys.

### Key Security

* Keys are hashed before storage — Duvo cannot retrieve your full key
* Keys can be revoked at any time by deleting them
* The `last_used_at` timestamp helps you identify unused keys
* Optional expiration can be set when creating keys programmatically

***

## Endpoints

> For Assignment and Build CRUD endpoints, see [Creating Assignments via API](https://docs.duvo.ai/running-assignments/creating-assignments-via-api).

### Runs

#### Start a Run

## POST /v1/runs

> Start a new agent run. Returns immediately with run info - does not wait for completion.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs":{"post":{"tags":["Runs"],"description":"Start a new agent run. Returns immediately with run info - does not wait for completion.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"agent_id":{"type":"string","format":"uuid","description":"The agent ID to run"},"message":{"type":"string","description":"Optional initial message to start the run with (e.g. a trigger payload or user instruction)"},"sandbox_id":{"type":"string","description":"Optional sandbox ID with pre-uploaded files"},"webhook_url":{"type":"string","format":"uri","description":"Webhook URL to POST events to (human_request, run_completed, run_failed, run_interrupted)"}},"required":["agent_id"],"additionalProperties":false}}},"required":true},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid"},"build_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"source":{"type":"string","nullable":true},"sandbox_id":{"type":"string","nullable":true,"description":"The sandbox ID for this run"},"started_at":{"type":"string","nullable":true},"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"updated_at":{"type":"string"},"human_in_the_loop_enabled":{"type":"boolean","description":"Whether this agent can request human input during execution"}},"required":["id","agent_id","build_id","status","source","sandbox_id","started_at","completed_at","created_at","updated_at","human_in_the_loop_enabled"],"additionalProperties":false}},"required":["run"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Get Run Status

## GET /v1/runs/{run\_id}

> Get information about an agent run. Can be polled to check status.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs/{run_id}":{"get":{"tags":["Runs"],"description":"Get information about an agent run. Can be polled to check status.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"run_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"run":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid"},"build_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"source":{"type":"string","nullable":true},"sandbox_id":{"type":"string","nullable":true,"description":"The sandbox ID for this run"},"started_at":{"type":"string","nullable":true},"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"updated_at":{"type":"string"},"human_in_the_loop_enabled":{"type":"boolean","description":"Whether this agent can request human input during execution"}},"required":["id","agent_id","build_id","status","source","sandbox_id","started_at","completed_at","created_at","updated_at","human_in_the_loop_enabled"],"additionalProperties":false,"nullable":true},"pending_human_request":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"created_at":{"type":"string"},"type":{"type":"string","enum":["approval","question"],"description":"Type of human request: 'approval' for yes/no decisions, 'question' for structured questions with optional predefined answers"},"questions":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"header":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"}},"required":["label"],"additionalProperties":false}},"multi_select":{"type":"boolean"}},"required":["question"],"additionalProperties":false},"description":"For 'question' type requests, the structured questions with optional predefined answer options"}},"required":["id","title","description","created_at","type"],"additionalProperties":false,"nullable":true}},"required":["run"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Get Run Messages

## GET /v1/runs/{run\_id}/messages

> Get paginated messages for an agent run. Messages are returned in chronological order.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs/{run_id}/messages":{"get":{"tags":["Runs"],"description":"Get paginated messages for an agent run. Messages are returned in chronological order.","parameters":[{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"in":"query","name":"limit","required":false,"description":"Number of messages per page (1-50, default 20)"},{"schema":{"type":"integer","minimum":0,"default":0},"in":"query","name":"offset","required":false,"description":"Number of messages to skip"},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"run_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"role":{"type":"string"},"timestamp":{"type":"string"},"text_content":{"type":"string"},"tool_call":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"arguments":{"type":"object","additionalProperties":{}}},"required":["id","name"],"additionalProperties":false},"tool_result":{"type":"object","properties":{"tool_call_id":{"type":"string"},"result":{},"error":{"type":"string"}},"required":["tool_call_id"],"additionalProperties":false}},"required":["id","type","role","timestamp"],"additionalProperties":false}},"total":{"type":"number","description":"Total number of messages for the run"},"limit":{"type":"number","description":"Number of messages per page"},"offset":{"type":"number","description":"Number of messages skipped"}},"required":["messages","total","limit","offset"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Post a Message to a Run

## POST /v1/runs/{run\_id}/messages

> Post a message to an agent run. This will persist the message and resume the agent execution if the run is in a resumable state (waiting, completed, or interrupted).

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs/{run_id}/messages":{"post":{"tags":["Runs"],"description":"Post a message to an agent run. This will persist the message and resume the agent execution if the run is in a resumable state (waiting, completed, or interrupted).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","minLength":1,"description":"The message content to send to the agent"}},"required":["message"],"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"run_id","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"role":{"type":"string"},"timestamp":{"type":"string"},"text_content":{"type":"string"}},"required":["id","type","role","timestamp","text_content"],"additionalProperties":false}},"required":["message"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Respond to Human Request

## POST /v1/runs/{run\_id}/human-requests/{request\_id}/respond

> Respond to a human-in-the-loop request. Use 'approved' (true/false) for approval-type requests, or 'answers' ({question: answer}) for question-type requests. Only works when the run is in 'waiting' status.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs/{run_id}/human-requests/{request_id}/respond":{"post":{"tags":["Runs"],"description":"Respond to a human-in-the-loop request. Use 'approved' (true/false) for approval-type requests, or 'answers' ({question: answer}) for question-type requests. Only works when the run is in 'waiting' status.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"type":"object","additionalProperties":{"type":"string"},"description":"For question-type requests: a map of question text to answer. Multi-select answers should be comma-separated."},"approved":{"type":"boolean","description":"For approval-type requests: true to approve, false to deny"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"run_id","required":true},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"request_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"human_request":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"run_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"response":{"type":"string","nullable":true},"responded_at":{"type":"string","nullable":true},"created_at":{"type":"string"}},"required":["id","run_id","title","description","response","responded_at","created_at"],"additionalProperties":false}},"required":["human_request"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Stop a Run

## POST /v1/runs/{run\_id}/stop

> Stop an agent run. No-op if the run is not currently running.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Runs","description":"Start, monitor, and manage agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/runs/{run_id}/stop":{"post":{"tags":["Runs"],"description":"Stop an agent run. No-op if the run is not currently running.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"run_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success"],"additionalProperties":false}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"401":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"502":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

### Sandboxes

#### Create a Sandbox

## POST /v1/sandboxes

> Create a new sandbox for file uploads. The sandbox can then be used when starting a run.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Sandboxes","description":"Create sandboxes and upload files for agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/sandboxes":{"post":{"tags":["Sandboxes"],"description":"Create a new sandbox for file uploads. The sandbox can then be used when starting a run.","responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"sandbox_id":{"type":"string"},"expires_at":{"type":"string"}},"required":["sandbox_id","expires_at"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Get Upload URLs for a Sandbox

## POST /v1/sandboxes/{sandbox\_id}/upload-urls

> Get a presigned URL for uploading a file to the sandbox. Use this for files larger than 10MB.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Sandboxes","description":"Create sandboxes and upload files for agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/sandboxes/{sandbox_id}/upload-urls":{"post":{"tags":["Sandboxes"],"description":"Get a presigned URL for uploading a file to the sandbox. Use this for files larger than 10MB.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string","minLength":1,"description":"Path where the file will be uploaded (e.g., /workspace/data.csv)"}},"required":["path"],"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"sandbox_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"upload_url":{"type":"string","format":"uri"},"path":{"type":"string"},"expires_in_seconds":{"type":"number"}},"required":["upload_url","path","expires_in_seconds"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Get Files in a Sandbox

## GET /v1/sandboxes/{sandbox\_id}/files

> List files in a sandbox directory.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Sandboxes","description":"Create sandboxes and upload files for agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/sandboxes/{sandbox_id}/files":{"get":{"tags":["Sandboxes"],"description":"List files in a sandbox directory.","parameters":[{"schema":{"type":"string","default":"/workspace"},"in":"query","name":"path","required":false},{"schema":{"type":"string","minLength":1},"in":"path","name":"sandbox_id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"},"type":{"type":"string","enum":["file","dir"]},"size_bytes":{"type":"number"}},"required":["name","path","type","size_bytes"],"additionalProperties":false}}},"required":["files"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

#### Upload Files to a Sandbox

## POST /v1/sandboxes/{sandbox\_id}/files

> Upload a file directly to the sandbox. Maximum file size is 10MB. For larger files, use the upload-urls endpoint.

```json
{"openapi":"3.0.3","info":{"title":"Duvo Public API","version":"1.0.0"},"tags":[{"name":"Sandboxes","description":"Create sandboxes and upload files for agent runs"}],"servers":[{"url":"https://api.duvo.ai","description":"Production server"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Get your API key from the Duvo dashboard."}}},"paths":{"/v1/sandboxes/{sandbox_id}/files":{"post":{"tags":["Sandboxes"],"description":"Upload a file directly to the sandbox. Maximum file size is 10MB. For larger files, use the upload-urls endpoint.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"sandbox_id","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"path":{"type":"string"},"size_bytes":{"type":"number"}},"required":["path","size_bytes"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"413":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}}}}}}
```

***

## Error Handling

All errors follow a consistent format:

```json
{
  "error": "Error type",
  "message": "Detailed error description"
}
```

**Common HTTP Status Codes**

| Code | Description                                              |
| ---- | -------------------------------------------------------- |
| 400  | Bad Request — Invalid parameters                         |
| 401  | Unauthorized — Missing or invalid API key                |
| 403  | Forbidden — API key doesn't have access to this resource |
| 404  | Not Found — Resource doesn't exist                       |
| 413  | Payload Too Large — File exceeds size limit              |
| 429  | Too Many Requests — Rate limit exceeded                  |
| 500  | Internal Server Error — Something went wrong on our end  |
| 502  | Bad Gateway — Upstream service unavailable               |

***

## Webhooks

### Human-in-the-Loop Webhook

When you provide a `human_request_webhook_url` when starting a run, Duvo will POST to that URL whenever the Assignment needs human input.

**Webhook Payload**

```json
{
  "event": "human_request_created",
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "request_id": "req_789xyz",
  "title": "Confirm data deletion",
  "description": "The agent wants to delete 150 records. Please confirm this action.",
  "created_at": "2024-01-15T10:32:00.000Z"
}
```

When you receive this webhook, use the [Respond to Human Request](#respond-to-human-request) endpoint to provide your response and resume the run.

***

## Complete Example

Here's a complete example of uploading files and starting a run:

```bash
#!/bin/bash
API_KEY="dv_your_api_key"
AGENT_ID="7c9e6679-7425-40de-944b-e07fc1f90ae7"
BASE_URL="https://api.duvo.ai/v1"

# 1. Create a sandbox
SANDBOX_RESPONSE=$(curl -s -X POST "$BASE_URL/sandboxes" \
  -H "Authorization: Bearer $API_KEY")
SANDBOX_ID=$(echo $SANDBOX_RESPONSE | jq -r '.sandbox_id')
echo "Created sandbox: $SANDBOX_ID"

# 2. Upload a file
curl -X POST "$BASE_URL/sandboxes/$SANDBOX_ID/files" \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@./input-data.csv" \
  -F "path=/workspace/input-data.csv"

# 3. Start a run with the sandbox
RUN_RESPONSE=$(curl -s -X POST "$BASE_URL/runs" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"agent_id\": \"$AGENT_ID\",
    \"sandbox_id\": \"$SANDBOX_ID\"
  }")
RUN_ID=$(echo $RUN_RESPONSE | jq -r '.run.id')
echo "Started run: $RUN_ID"

# 4. Poll for completion
while true; do
  STATUS_RESPONSE=$(curl -s -X GET "$BASE_URL/runs/$RUN_ID" \
    -H "Authorization: Bearer $API_KEY")
  STATUS=$(echo $STATUS_RESPONSE | jq -r '.run.status')
  echo "Run status: $STATUS"

  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "stopped" ]; then
    break
  fi

  sleep 5
done

echo "Run finished with status: $STATUS"

# 5. Get the conversation messages
curl -s -X GET "$BASE_URL/runs/$RUN_ID/messages?limit=50" \
  -H "Authorization: Bearer $API_KEY" | jq '.messages'
```
