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.
What you can do with the API
| Goal | Entities involved |
|---|
| Start a Job from your application or pipeline | Jobs |
| Create and update Assignments programmatically | Assignments, Revisions |
| Sync SOPs from a Git repository into Duvo | Skills |
| Push work items into a queue from an external system | Queues, Cases |
| Schedule recurring Jobs | Schedules |
| Connect and provision Connections without the UI | Connections |
| Upload files for an Assignment to process | Sandboxes |
Authentication
All requests use a team-scoped API key in the Authorization header:
curl https://api.duvo.ai/v2/teams/$TEAM_ID/agents \
-H "Authorization: Bearer $DUVO_API_KEY"
Generate a key at Team Settings → API Keys in the Duvo dashboard. Keys are team-scoped and inherit the permissions of the user who created them — only Admins and Owners can create them. Keys 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).
For the full auth, rate-limit, and error reference, see Running Assignments via API.
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\}).
Assignments and Revisions
Assignments are the units of work in Duvo. A Revision is a versioned snapshot of an Assignment’s Setup — SOP, model settings, and attached skills. Jobs always run against an Assignment’s latest Revision.
What you can do:
- List, create, and update Assignments
- Create Revisions to deploy SOP changes programmatically
- Organize Assignments into folders
| Action | Method | Path |
|---|
| List Assignments | GET | /teams/{teamId}/agents |
| Create an Assignment | POST | /teams/{teamId}/agents |
| Get an Assignment | GET | /agents/{agent_id} |
| Update an Assignment | PATCH | /agents/{agent_id} |
| List Revisions | GET | /agents/{agent_id}/revisions |
| Create a Revision | POST | /agents/{agent_id}/revisions |
See the API Reference for request and response schemas, and Creating Assignments via API for worked examples including folders and the full create flow.
You can create an Assignment and its first Revision in a single request — pass a build object in the body:
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 Assignments 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 Assignments to folder | POST | /teams/{teamId}/agent-folders/move-agents |
Jobs
A Job is a single execution of an Assignment. Jobs are asynchronous — the API returns immediately and you poll or use a webhook to track completion.
What you can do:
- Start a Job 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 Assignment did, step by step
- Respond to human-in-the-loop requests programmatically
- Stop a running Job
| Action | Method | Path |
|---|
| Start a Job | POST | /teams/{teamId}/runs |
| List Jobs | GET | /teams/{teamId}/runs |
| Get Job 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 Job | POST | /runs/{run_id}/stop |
See the API Reference for schemas, and Running Assignments via API for the full flow including file uploads and HITL webhooks.
Starting a Job and polling for completion:
#!/bin/bash
# Start a Job
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 Job 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 Assignment 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 — SOPs, rule books, taxonomies — into a zip that any Assignment can use. Managing skills via the API lets you keep your SOPs 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 for schemas and Creating Custom Skills for how to structure your SKILL.md files.
Creating a skill from text:
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.
# 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.
#!/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 Assignments 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 Assignment 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, delegate, retry, or 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-delegate Cases | POST | /queues/{queue_id}/cases/bulk-delegate |
| Bulk-retry Cases | POST | /queues/{queue_id}/cases/bulk-retry |
| Delete a Case | DELETE | /cases/{case_id} |
See the API Reference for schemas.
Case statuses:
| Status | Meaning |
|---|
pending | Waiting to be picked up |
claimed | An Assignment 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 — Assignments receive it when they claim the Case:
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:
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 Assignment with a Case Trigger picks them up automatically — see Case Queue for how to configure the trigger.
Validate case-queue wiring before you start work. Check that a Revision’s case-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 Jobs.
| Action | Method | Path |
|---|
| Validate a Revision’s case queue | GET | /agents/{agent_id}/revisions/{build_id}/case-queue-validation |
Schedules
You can create, list, update, and delete an Assignment’s schedules via the API to run recurring Jobs.
| 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 for schedule fields and limits.
Connections
Connections link your Assignments 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 |
| Start Composio OAuth (Slack, HubSpot…) | POST | /teams/{teamId}/connections/composio/start |
| Finalize Composio OAuth | POST | /teams/{teamId}/connections/composio/finalize |
| Probe a custom MCP server | POST | /teams/{teamId}/connections/mcp/probe |
See the API Reference for schemas and Creating Assignments via API for the full OAuth flows.
Sandboxes and file uploads
When your Assignment needs to process files — CSVs, PDFs, images — upload them to a Sandbox before starting the Job, 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 for schemas and 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:
OMS → POST /queues/{queue_id}/cases (one case per order)
Duvo Assignment (Case Trigger) → picks up case, processes it
Assignment → calls OMS webhook with result
Step 1: Configure Duvo. Create an Assignment with a Case Queue trigger pointed at your order-processing queue. The Assignment’s SOP instructs it to read Case data, validate the order, check inventory via your ERP connection, and post the result to your OMS webhook.
Step 2: OMS integration code.
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 3: Track results. List Cases to see their status and retrieve Assignment output:
# 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}'
Next steps