> ## 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.

# Developer Platform API

> Drive Duvo from your own code — start Runs, manage Agents and Revisions, sync Skills, push Cases, and connect services programmatically.

The Duvo Public API lets you drive Duvo from your own code, scripts, and pipelines. This page is the orientation hub: what you can do, which endpoints to reach for, and how to put them together into real integrations. For request and response schemas, follow the links into the [API Reference](/api-reference).

***

## What you can do with the API

| Goal                                                 | Entities involved |
| ---------------------------------------------------- | ----------------- |
| Start a Run from your application or pipeline        | Runs              |
| Create and update Agents programmatically            | Agents, Revisions |
| Sync AOPs from a Git repository into Duvo            | Skills            |
| Push work items into a queue from an external system | Queues, Cases     |
| Schedule recurring Runs                              | Schedules         |
| Connect and provision Connections without the UI     | Connections       |
| Upload files for an Agent to process                 | Sandboxes         |

***

## Authentication

All requests use an API key in the `Authorization` header:

```bash theme={"dark"}
curl https://api.duvo.ai/v2/teams/$TEAM_ID/agents \
  -H "Authorization: Bearer $DUVO_API_KEY"
```

Generate a key at **Your Profile → API keys** in the Duvo dashboard — scope it to a single team or to all teams you can access. Users with the Manager role or above can also create keys for a specific team at **Team Settings → API keys**. Keys inherit the permissions of the user who created them, use the format `dv_<random>`, and are shown once at creation, so store them immediately.

* **Base URL:** `https://api.duvo.ai/v2`
* **Rate limit:** 300 requests per minute per API key. Responses include `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset` headers. On `429`, honor the `retry-after` header.
* **Error model:** errors return `{ "error": "...", "message": "..." }` with a standard HTTP status code (`400`, `401`, `403`, `404`, `413`, `429`, `5xx`).

<Warning>
  API keys are shown once at creation and inherit the permissions of the user
  who created them. Store them securely, treat them like a password, and stay
  under the 300 requests per minute per-key rate limit — on a `429`, honor the
  `retry-after` header.
</Warning>

For the full auth, rate-limit, and error reference, see [Running Agents via API](/user-guide/running-assignments/running-assignments-via-api).

<Note>
  Collection endpoints are team-scoped (`/teams/\{teamId\}/...`); endpoints that
  act on a specific resource are addressed directly by ID (`/agents/\{agent_id\}`,
  `/runs/\{run_id\}`).
</Note>

***

## Agents and Revisions

Agents are the units of work in Duvo. A Revision is a versioned snapshot of an Agent's Setup — AOP, model settings, and attached skills. Runs always run against an Agent's latest Revision.

**What you can do:**

* List, create, and update Agents
* Create Revisions to deploy AOP changes programmatically
* Organize Agents into folders

| Action            | Method  | Path                           |
| ----------------- | ------- | ------------------------------ |
| List Agents       | `GET`   | `/teams/{teamId}/agents`       |
| Create an Agent   | `POST`  | `/teams/{teamId}/agents`       |
| Get an Agent      | `GET`   | `/agents/{agent_id}`           |
| Update an Agent   | `PATCH` | `/agents/{agent_id}`           |
| List Revisions    | `GET`   | `/agents/{agent_id}/revisions` |
| Create a Revision | `POST`  | `/agents/{agent_id}/revisions` |

See the [API Reference](/api-reference) for request and response schemas, and [Creating Agents via API](/user-guide/running-assignments/creating-assignments-via-api) for worked examples including folders and the full create flow.

You can create an Agent and its first Revision in a single request — pass a `build` object in the body:

```bash theme={"dark"}
curl -X POST https://api.duvo.ai/v2/teams/$TEAM_ID/agents \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Invoice Processor",
    "build": {
      "name": "v1",
      "config": {
        "version": "v2",
        "data": {
          "input": "Process invoices from uploaded files, extract line items, and post a summary to Slack."
        }
      }
    }
  }'
```

**Organize Agents into folders:**

| Action                | Method   | Path                                        |
| --------------------- | -------- | ------------------------------------------- |
| List folders          | `GET`    | `/teams/{teamId}/agent-folders`             |
| Create a folder       | `POST`   | `/teams/{teamId}/agent-folders`             |
| Update a folder       | `PATCH`  | `/agent-folders/{folder_id}`                |
| Delete a folder       | `DELETE` | `/agent-folders/{folder_id}`                |
| Move Agents to folder | `POST`   | `/teams/{teamId}/agent-folders/move-agents` |

***

## Runs

A Run is a single execution of an Agent. Runs are asynchronous — the API returns immediately and you poll or use a webhook to track completion.

```mermaid theme={"dark"}
sequenceDiagram
    participant You as Your code
    participant API as Duvo API
    participant Agent
    You->>API: POST /teams/{teamId}/runs (start a Run)
    API-->>You: 200 — run.id, status: pending
    API->>Agent: dispatch Run to latest Revision
    Agent->>Agent: run (status: running / waiting)
    Note over You,Agent: Poll GET /runs/{run_id} or receive a webhook
    Agent-->>API: finishes (completed / failed / stopped)
    API-->>You: status: completed
```

**What you can do:**

* Start a Run with optional file input, an initial message, and a webhook for event notifications
* Poll for status (`pending`, `running`, `waiting`, `completed`, `failed`, `stopped`)
* Read the conversation — everything the Agent did, step by step
* Respond to human-in-the-loop requests programmatically
* Stop a running Run

| Action                  | Method | Path                                                 |
| ----------------------- | ------ | ---------------------------------------------------- |
| Start a Run             | `POST` | `/teams/{teamId}/runs`                               |
| List Runs               | `GET`  | `/teams/{teamId}/runs`                               |
| Get Run status          | `GET`  | `/runs/{run_id}`                                     |
| Read messages           | `GET`  | `/runs/{run_id}/messages`                            |
| Send a message          | `POST` | `/runs/{run_id}/messages`                            |
| Respond to HITL request | `POST` | `/runs/{run_id}/human-requests/{request_id}/respond` |
| Stop a Run              | `POST` | `/runs/{run_id}/stop`                                |

See the [API Reference](/api-reference) for schemas, and [Running Agents via API](/user-guide/running-assignments/running-assignments-via-api) for the full flow including file uploads and HITL webhooks.

**Starting a Run and polling for completion:**

```bash theme={"dark"}
#!/bin/bash
# Start a Run
RUN=$(curl -s -X POST https://api.duvo.ai/v2/teams/$TEAM_ID/runs \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"agent_id\": \"$AGENT_ID\"}")
RUN_ID=$(echo $RUN | jq -r '.run.id')

# Poll until the Run finishes
while true; do
  STATUS=$(curl -s https://api.duvo.ai/v2/runs/$RUN_ID \
    -H "Authorization: Bearer $DUVO_API_KEY" | jq -r '.run.status')
  [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "stopped" ] && break
  sleep 5
done

# Read what the Agent did
curl -s "https://api.duvo.ai/v2/runs/$RUN_ID/messages?limit=100" \
  -H "Authorization: Bearer $DUVO_API_KEY" | jq '.messages[]'
```

***

## Skills

Skills package reusable knowledge — AOPs, rule books, taxonomies — into a zip that any Agent can use. Managing skills via the API lets you keep your AOPs in a Git repository and sync them to Duvo on every push, without anyone clicking through the UI.

**What you can do:**

* List team skills and system skills
* Create a skill from text content
* Upload a multi-file skill as a zip
* Download a skill as a zip
* Update individual skill files
* Delete a skill

| Action                | Method   | Path                              |
| --------------------- | -------- | --------------------------------- |
| List team skills      | `GET`    | `/teams/{teamId}/skills`          |
| List system skills    | `GET`    | `/skills/system`                  |
| Create a skill (text) | `POST`   | `/teams/{teamId}/skills`          |
| Upload a skill (zip)  | `POST`   | `/teams/{teamId}/skills/upload`   |
| Get skill files       | `GET`    | `/skills/{skill_id}/files`        |
| Get a skill file      | `GET`    | `/skills/{skill_id}/files/{path}` |
| Update a skill file   | `PUT`    | `/skills/{skill_id}/files/{path}` |
| Download skill as zip | `GET`    | `/skills/{skill_id}/download`     |
| Delete a skill        | `DELETE` | `/skills/{skill_id}`              |

See the [API Reference](/api-reference) for schemas and [Creating Custom Skills](/user-guide/skills/creating-custom-skills) for how to structure your `SKILL.md` files.

**Creating a skill from text:**

```bash theme={"dark"}
curl -X POST https://api.duvo.ai/v2/teams/$TEAM_ID/skills \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "refund-policy",
    "description": "Guidelines for processing customer refund requests. Use when evaluating refund eligibility or drafting refund responses.",
    "content": "---\nname: refund-policy\ndescription: Guidelines for processing customer refund requests.\n---\n\n# Refund Policy\n\nRefunds are accepted within 30 days of purchase..."
  }'
```

**Uploading a multi-file skill as a zip:**

A zip must contain a `SKILL.md` at the root with `name` and `description` frontmatter. Supporting documents, templates, and examples can live in subfolders.

```bash theme={"dark"}
# Zip your skill folder first
cd my-skill && zip -r ../my-skill.zip . && cd ..

curl -X POST https://api.duvo.ai/v2/teams/$TEAM_ID/skills/upload \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -F "file=@my-skill.zip"
```

**Syncing skills from a Git repository (CI example):**

This script pushes a directory of skill folders to Duvo on every deploy. It deletes stale skills by name and re-creates them from the latest source.

```bash theme={"dark"}
#!/bin/bash
# skills-sync.sh — run on every push to your skills repo
DUVO_API_KEY="${DUVO_API_KEY}"
TEAM_ID="${DUVO_TEAM_ID}"
BASE_URL="https://api.duvo.ai/v2"
SKILLS_DIR="./skills"  # directory of skill folders

# Fetch existing team skills
EXISTING=$(curl -s "$BASE_URL/teams/$TEAM_ID/skills" \
  -H "Authorization: Bearer $DUVO_API_KEY")

for SKILL_DIR in "$SKILLS_DIR"/*/; do
  SKILL_NAME=$(basename "$SKILL_DIR")

  # Delete the old version if it exists
  OLD_ID=$(echo $EXISTING | jq -r ".skills[] | select(.name == \"$SKILL_NAME\") | .id")
  if [ -n "$OLD_ID" ] && [ "$OLD_ID" != "null" ]; then
    curl -s -X DELETE "$BASE_URL/skills/$OLD_ID" \
      -H "Authorization: Bearer $DUVO_API_KEY"
    echo "Deleted old version of $SKILL_NAME"
  fi

  # Zip and upload the updated skill
  cd "$SKILL_DIR" && zip -r "/tmp/${SKILL_NAME}.zip" . && cd -
  RESULT=$(curl -s -X POST "$BASE_URL/teams/$TEAM_ID/skills/upload" \
    -H "Authorization: Bearer $DUVO_API_KEY" \
    -F "file=@/tmp/${SKILL_NAME}.zip")
  echo "Uploaded $SKILL_NAME: $(echo $RESULT | jq -r '.skill.id')"
done

echo "Skill sync complete."
```

***

## Queues and Cases

Queues hold individual work items — called Cases — that Agents process one at a time. You can push Cases into a queue from any external system, which makes it the standard pattern for high-volume integrations where events arrive faster than a single Agent can handle them.

**What you can do:**

* List and inspect queues
* Push Cases into a queue (one at a time or in batch)
* List and search Cases in a queue
* Bulk-update, bulk-reprocess, or bulk-delete Cases
* Track Case status through the processing lifecycle

| Action                  | Method   | Path                                          |
| ----------------------- | -------- | --------------------------------------------- |
| List queues             | `GET`    | `/teams/{teamId}/queues`                      |
| Get a queue             | `GET`    | `/queues/{queue_id}`                          |
| List Cases in a queue   | `GET`    | `/queues/{queue_id}/cases`                    |
| Search Cases            | `POST`   | `/queues/{queue_id}/cases/search`             |
| Create Cases            | `POST`   | `/queues/{queue_id}/cases`                    |
| Bulk-update Case status | `POST`   | `/queues/{queue_id}/cases/bulk-update-status` |
| Bulk-reprocess Cases    | `POST`   | `/queues/{queue_id}/cases/bulk-reprocess`     |
| Bulk-delete Cases       | `POST`   | `/queues/{queue_id}/cases/bulk-delete`        |
| Delete a Case           | `DELETE` | `/cases/{case_id}`                            |

See the [API Reference](/api-reference) for schemas.

**Case statuses:**

| Status      | Meaning                            |
| ----------- | ---------------------------------- |
| `pending`   | Waiting to be picked up            |
| `claimed`   | An Agent is actively working on it |
| `completed` | Processing finished successfully   |
| `failed`    | Processing failed                  |

When listing Cases you can also filter by `needs_input` (Cases with an open human request) and `postponed` (Cases scheduled to retry later). These are derived from Case fields (`pending_human_request_id`, `postponed_to`) rather than stored status values.

**Pushing a Case from an external system.** Wrap a single Case in a `case` object. The `data` field is free-form text or a JSON string — Agents receive it when they claim the Case:

```bash theme={"dark"}
curl -X POST "https://api.duvo.ai/v2/queues/$QUEUE_ID/cases" \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "case": {
      "title": "Order #ORD-20240115-4821",
      "data": "{\"order_id\":\"ORD-20240115-4821\",\"customer_email\":\"buyer@example.com\",\"total\":89.97,\"notes\":\"Gift wrap requested\"}"
    }
  }'
```

**Batch-pushing Cases for high-volume intake.** Use a `cases` array instead:

```bash theme={"dark"}
curl -X POST "https://api.duvo.ai/v2/queues/$QUEUE_ID/cases" \
  -H "Authorization: Bearer $DUVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cases": [
      { "title": "Invoice #INV-001", "data": "{\"supplier\":\"ACME\",\"amount\":1200.00}" },
      { "title": "Invoice #INV-002", "data": "{\"supplier\":\"GlobalParts\",\"amount\":580.50}" },
      { "title": "Invoice #INV-003", "data": "{\"supplier\":\"ShipFast\",\"amount\":345.00}" }
    ]
  }'
```

The response returns the created Cases under `added_cases`, each with its `id` and `status`. Once Cases are in the queue, an Agent with a Case Trigger picks them up automatically — see [Queue](/user-guide/assignment-features/case-queue) for how to configure the trigger.

**Validate queue wiring before you start work.** Check that a Revision's queue integration slots are linked to real queues. The response reports, per producer/consumer slot, how many queues are linked. A slot with `linked_queue_count` of `0` is attached but points at no queue and will fail at runtime — link a queue before starting Runs.

| Action                      | Method | Path                                                            |
| --------------------------- | ------ | --------------------------------------------------------------- |
| Validate a Revision's queue | `GET`  | `/agents/{agent_id}/revisions/{build_id}/case-queue-validation` |

***

## Schedules

You can create, list, update, and delete an Agent's schedules via the API to run recurring Runs.

| Action            | Method   | Path                                         |
| ----------------- | -------- | -------------------------------------------- |
| List schedules    | `GET`    | `/agents/{agent_id}/schedules`               |
| Create a schedule | `POST`   | `/agents/{agent_id}/schedules`               |
| Update a schedule | `PATCH`  | `/agents/{agent_id}/schedules/{schedule_id}` |
| Delete a schedule | `DELETE` | `/agents/{agent_id}/schedules/{schedule_id}` |

See the [API Reference](/api-reference) for schedule fields and limits.

***

## Connections

Connections link your Agents to external services. The API lets you list and inspect connections, create user-provided connections (custom MCP servers with an API key), and initiate OAuth flows.

| Action                                       | Method   | Path                                                        |
| -------------------------------------------- | -------- | ----------------------------------------------------------- |
| List connections                             | `GET`    | `/teams/{teamId}/connections`                               |
| Get a connection                             | `GET`    | `/connections/{connection_id}`                              |
| Create a connection (user-provided)          | `POST`   | `/teams/{teamId}/connections`                               |
| Update a connection                          | `PATCH`  | `/connections/{connection_id}`                              |
| Delete a connection                          | `DELETE` | `/connections/{connection_id}`                              |
| Start native OAuth (Gmail, Sheets, Outlook…) | `POST`   | `/teams/{teamId}/connections/oauth/native/{provider}/start` |
| Probe a connection's tool catalog            | `POST`   | `/teams/{teamId}/connections/mcp/probe`                     |

Probe by `integration_slug` (catalog connections — the backend resolves the server URL for you) or by a custom `server_url`. A response with `success: false` includes a `code`: `listing_unavailable` means there's nothing to list yet (not an error — some connections only expose tools once connected), while `probe_failed` means the probe genuinely failed.

See the [API Reference](/api-reference) for schemas and [Creating Agents via API](/user-guide/running-assignments/creating-assignments-via-api) for the full OAuth flows.

***

## Sandboxes and file uploads

When your Agent needs to process files — CSVs, PDFs, images — upload them to a Sandbox before starting the Run, then pass the `sandbox_id` when you start the run.

| Action           | Method | Path                                  |
| ---------------- | ------ | ------------------------------------- |
| Create a Sandbox | `POST` | `/sandboxes`                          |
| Get upload URLs  | `POST` | `/sandboxes/{sandbox_id}/upload-urls` |
| Upload a file    | `POST` | `/sandboxes/{sandbox_id}/files`       |
| List files       | `GET`  | `/sandboxes/{sandbox_id}/files`       |

See the [API Reference](/api-reference) for schemas and [Running Agents via API](/user-guide/running-assignments/running-assignments-via-api) for the complete file-upload pattern.

***

## End-to-end example: order management integration

This example shows an order management system (OMS) pushing new orders into Duvo for processing whenever they arrive. Duvo validates each order, checks inventory, and posts the result back to the OMS.

**Architecture:**

```mermaid theme={"dark"}
flowchart LR
    OMS["OMS"] -->|"POST /queues/{queue_id}/cases (one case per order)"| Queue["Duvo Queue"]
    Queue --> Agent["Duvo Agent (Case Trigger)<br/>picks up case, processes it"]
    Agent -->|"calls OMS webhook with result"| OMS
```

<Steps>
  <Step title="Configure Duvo">
    Create an Agent with a Queue trigger pointed at your order-processing queue. The Agent's AOP instructs it to read Case data, validate the order, check inventory via your ERP connection, and post the result to your OMS webhook.
  </Step>

  <Step title="OMS integration code">
    ```typescript theme={"dark"}
    const DUVO_API_KEY = process.env.DUVO_API_KEY;
    const DUVO_QUEUE_ID = process.env.DUVO_ORDER_QUEUE_ID;
    const BASE_URL = "https://api.duvo.ai/v2";

    interface Order {
      orderId: string;
      customerEmail: string;
      items: Array<{ sku: string; quantity: number; unitPrice: number }>;
      total: number;
    }

    async function submitOrderToDuvo(order: Order): Promise<string> {
      const response = await fetch(`${BASE_URL}/queues/${DUVO_QUEUE_ID}/cases`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${DUVO_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          case: {
            title: `Order ${order.orderId}`,
            data: JSON.stringify(order),
          },
        }),
      });

      if (!response.ok) {
        const err = await response.json();
        throw new Error(`Duvo API error: ${err.error} — ${err.message}`);
      }

      const { added_cases } = await response.json();
      return added_cases[0].id;
    }

    // Called when a new order arrives in the OMS
    async function onNewOrder(order: Order) {
      const caseId = await submitOrderToDuvo(order);
      console.info(`Order ${order.orderId} submitted as Case ${caseId}`);
    }
    ```
  </Step>

  <Step title="Track results">
    List Cases to see their status and retrieve Agent output:

    ```bash theme={"dark"}
    # Check failed orders
    curl "https://api.duvo.ai/v2/queues/$QUEUE_ID/cases?status=failed" \
      -H "Authorization: Bearer $DUVO_API_KEY" | jq '.cases[] | {id, title, status}'
    ```
  </Step>
</Steps>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Creating Agents via API" href="/user-guide/running-assignments/creating-assignments-via-api">
    Agents, Revisions, Folders, Connections, Schedules
  </Card>

  <Card title="Running Agents via API" href="/user-guide/running-assignments/running-assignments-via-api">
    Runs, Sandboxes, HITL webhooks
  </Card>

  <Card title="Creating Custom Skills" href="/user-guide/skills/creating-custom-skills">
    How to structure skills for the API
  </Card>

  <Card title="Queue" href="/user-guide/assignment-features/case-queue">
    Queue architecture and Case Trigger setup
  </Card>

  <Card title="API Reference" href="/api-reference">
    Full request and response schemas for every endpoint
  </Card>
</CardGroup>
