> ## Documentation Index
> Fetch the complete documentation index at: https://docs.duvo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Running Agents via API

> Start Runs, upload files via Sandboxes, handle Human-in-the-Loop webhooks, and manage authentication for the Duvo Public API.

## Overview

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

### Finding Your Agent ID

Each Agent has a unique ID you pass in every API request. The easiest way to find it is in the agent builder: open your agent, go to the **Setup** tab, scroll to the **Triggers** section, and expand the **API Trigger** card. It shows a ready-to-run `curl` example with your agent's ID already filled in, so you can copy and paste it straight into a terminal or script. You can also create an API key from the link in that card.

### Key Concepts

**Agents**: Agents are configured through the Duvo web interface with a specific AOP, connections, and capabilities. The Public API allows you to start Runs for them programmatically. To create and manage Agents via API, see [Creating Agents via API](/user-guide/running-assignments/creating-assignments-via-api).

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

**Runs**: A run represents a single execution of an Agent. When you start a run, the Agent 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 Agent 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 Agents 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/v2
```

### 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 single team or to all teams the owner can access, 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 theme={"dark"}
curl -X GET "https://api.duvo.ai/health" \
  -H "Authorization: Bearer dv_your_api_key_here"
```

### Creating API Keys in the UI

<Steps>
  <Step title="Open API keys settings">
    Navigate to **Your Profile** → **API keys** in the Duvo web interface.
  </Step>

  <Step title="Create a key">
    Click **Create API key**.
  </Step>

  <Step title="Name the key">
    Enter a descriptive name for the key (e.g., "Production Pipeline", "CI/CD Integration").
  </Step>

  <Step title="Choose the key's access">
    Choose **All teams I can access** or **Single team**.
  </Step>

  <Step title="Confirm">
    Click **Create**.
  </Step>

  <Step title="Copy it immediately">
    **Copy the key immediately** — it will only be shown once and cannot be retrieved later.
  </Step>
</Steps>

Users with the Manager role or above can also create keys from **Team Settings** → **API keys**; keys created there are always scoped to that team.

<Warning>
  Store your API key securely. Treat it like a password. If you lose it, you'll
  need to create a new one.
</Warning>

### Managing API Keys

* From **Your Profile → API keys**, view and delete every key you created, across all your teams
* From **Team Settings → API keys**, users with the Manager role or above can view and delete all of that team's keys, including who created them and when they were last used

Delete any key that is no longer needed or may have been compromised.

### 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 Agent and Revision CRUD endpoints, see [Creating Agents via API](/user-guide/running-assignments/creating-assignments-via-api).

### Runs

#### Start a Run

> **API**: `POST /teams/{teamId}/runs` — see the [API Reference](/api-reference) for full details.

#### Get Run Status

> **API**: `GET /runs/{run_id}` — see the [API Reference](/api-reference) for full details.

#### Get Run Messages

> **API**: `GET /runs/{run_id}/messages` — see the [API Reference](/api-reference) for full details.

#### Post a Message to a Run

> **API**: `POST /runs/{run_id}/messages` — see the [API Reference](/api-reference) for full details.

#### Respond to Human Request

> **API**: `POST /runs/{run_id}/human-requests/{request_id}/respond` — see the [API Reference](/api-reference) for full details.

#### Stop a Run

> **API**: `POST /runs/{run_id}/stop` — see the [API Reference](/api-reference) for full details.

### Sandboxes

#### Create a Sandbox

> **API**: `POST /sandboxes` — see the [API Reference](/api-reference) for full details.

#### Get Upload URLs for a Sandbox

> **API**: `POST /sandboxes/{sandbox_id}/upload-urls` — see the [API Reference](/api-reference) for full details.

#### Get Files in a Sandbox

> **API**: `GET /sandboxes/{sandbox_id}/files` — see the [API Reference](/api-reference) for full details.

#### Upload Files to a Sandbox

> **API**: `POST /sandboxes/{sandbox_id}/files` — see the [API Reference](/api-reference) for full details.

***

## Error Handling

All errors follow a consistent format:

```json theme={"dark"}
{
  "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 Agent needs human input.

```mermaid theme={"dark"}
sequenceDiagram
    participant Agent
    participant Duvo
    participant You as Your endpoint
    Agent->>Duvo: needs human input (Run status: waiting)
    Duvo->>You: POST human_request_webhook_url (human_request_created)
    You->>Duvo: POST /runs/{run_id}/human-requests/{request_id}/respond
    Duvo->>Agent: resume Run
```

**Webhook Payload**

```json theme={"dark"}
{
  "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 theme={"dark"}
#!/bin/bash
API_KEY="dv_your_api_key"
TEAM_ID="your-team-id"
AGENT_ID="7c9e6679-7425-40de-944b-e07fc1f90ae7"
BASE_URL="https://api.duvo.ai/v2"

# 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/teams/$TEAM_ID/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'
```
