The Duet REST API is served by the core Hono server on port 7777 by default. All endpoints return JSON. The base URL is http://localhost:7777 for local development.
Authentication
Duet supports two authentication methods:
- Human users - session cookie (
duet_session) obtained via the login endpoint. - Agents - API key passed via the
Authorization header using Bearer token format.
# Human: login to get a session cookie
curl -X POST http://localhost:7777/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password": "your-password"}' \
-c cookies.txt
# Agent: use Bearer token
curl http://localhost:7777/api/notes \
-H "Authorization: Bearer duet_xxxxx"
Auth Endpoints
| Method | Path | Description |
|---|
| POST | /api/auth/setup | Set initial password (first run only) |
| POST | /api/auth/login | Login with password, returns session cookie |
| POST | /api/auth/logout | Clear session cookie |
| GET | /api/auth/me | Check current auth state |
# Initial setup (first run)
curl -X POST http://localhost:7777/api/auth/setup \
-H "Content-Type: application/json" \
-d '{"password": "your-secure-password"}'
# Check auth state
curl http://localhost:7777/api/auth/me -b cookies.txt
Notes
All note endpoints require authentication.
| Method | Path | Description |
|---|
| POST | /api/notes | Create a new note |
| GET | /api/notes | List notes with optional filters |
| GET | /api/notes/search | Full-text search notes |
| GET | /api/notes/:id | Read a single note |
| PATCH | /api/notes/:id | Update a note |
| POST | /api/notes/:id/archive | Archive a note |
| POST | /api/notes/:id/unarchive | Unarchive a note |
| DELETE | /api/notes/:id | Delete a note (humans only) |
| GET | /api/notes/:id/versions | Get version history |
| GET | /api/notes/:id/versions/:versionNumber | Get a specific version |
Create Note
POST /api/notes
Content-Type: application/json
{
"title": "Meeting Notes",
"content": "## Attendees\n- Alice\n- Bob",
"tags": ["meetings", "engineering"]
}
List Notes
Query parameters:
| Parameter | Type | Description |
|---|
| authorType | string | "human" or "agent" |
| authorName | string | Specific author name |
| tagName | string | Filter by tag name |
| isArchived | boolean | Include archived notes |
| limit | number | Max results (default 50) |
| offset | number | Pagination offset (default 0) |
GET /api/notes?authorType=agent&tagName=engineering&limit=10
Search Notes
GET /api/notes/search?q=deployment+checklist&limit=5
Update Note
PATCH /api/notes/:id
Content-Type: application/json
{
"title": "Updated Title",
"content": "New content here",
"tags": ["updated-tag"]
}
Todos
| Method | Path | Description |
|---|
| POST | /api/todos | Create a new todo |
| GET | /api/todos | List todos with optional filters |
| GET | /api/todos/:id | Read a single todo |
| PATCH | /api/todos/:id | Update a todo |
| DELETE | /api/todos/:id | Delete a todo (humans only) |
Create Todo
POST /api/todos
Content-Type: application/json
{
"title": "Review PR #42",
"priority": "high",
"dueDate": "2026-03-20T17:00:00Z",
"noteId": "optional-note-uuid",
"assigneeType": "human",
"assigneeName": "emre"
}
List Todos
Query parameters:
| Parameter | Type | Description |
|---|
| status | string | "pending" or "done" |
| priority | string | "low", "medium", "high", or "urgent" |
| limit | number | Max results (default 50) |
GET /api/todos?status=pending&priority=high
Tags
Tag management is restricted to human users only.
| Method | Path | Description |
|---|
| POST | /api/tags | Create a tag (humans only) |
| GET | /api/tags | List all tags |
| PATCH | /api/tags/:id | Update a tag (humans only) |
| DELETE | /api/tags/:id | Delete a tag (humans only) |
POST /api/tags
Content-Type: application/json
{
"name": "engineering",
"color": "#3b82f6"
}
Agents
Agent management is restricted to human users only. When creating an agent, the API key is returned in the response and is only shown once.
| Method | Path | Description |
|---|
| POST | /api/agents | Register a new agent (returns API key) |
| GET | /api/agents | List all registered agents |
| POST | /api/agents/:id/deactivate | Deactivate an agent |
| POST | /api/agents/:id/reactivate | Reactivate an agent |
| DELETE | /api/agents/:id | Delete an agent (humans only) |
POST /api/agents
Content-Type: application/json
{
"name": "claude",
"permissions": ["read", "write", "archive"]
}
// Response:
{
"agent": { "id": "...", "name": "claude", ... },
"apiKey": "duet_xxxxx" // Only shown once!
}
Activity
| Method | Path | Description |
|---|
| GET | /api/activity | Get activity feed |
Query parameters:
| Parameter | Type | Description |
|---|
| limit | number | Max entries (default 20) |
| entityType | string | "note", "todo", or "agent_key" |
| actorName | string | Filter by actor name |
GET /api/activity?actorName=claude&entityType=note&limit=10
Settings
Settings are key-value pairs managed by human users only.
| Method | Path | Description |
|---|
| GET | /api/settings/:key | Read a setting (humans only) |
| PUT | /api/settings/:key | Write a setting (humans only) |
PUT /api/settings/theme
Content-Type: application/json
{
"value": "dark"
}
Calendar
Google Calendar integration endpoints. See the Google Calendar Sync guide for setup instructions.
| Method | Path | Description |
|---|
| GET | /api/calendar/auth-url | Get Google Calendar OAuth URL |
| GET | /api/calendar/callback | OAuth callback (redirect target) |
| GET | /api/calendar/status | Check calendar connection status |
| POST | /api/calendar/sync | Trigger a manual sync |
| POST | /api/calendar/disconnect | Disconnect Google Calendar |
Uploads
| Method | Path | Description |
|---|
| POST | /api/uploads | Upload a file (multipart/form-data) |
| GET | /api/uploads/:filename | Serve an uploaded file |
# Upload a file
curl -X POST http://localhost:7777/api/uploads \
-b cookies.txt \
-F "file=@screenshot.png"
# Response:
{
"filename": "abc123-screenshot.png",
"url": "/api/uploads/abc123-screenshot.png"
}
Health
| Method | Path | Description |
|---|
| GET | /api/health | Health check (no auth required) |
GET /api/health
// Response:
{ "status": "ok" }
Error Responses
All errors follow a consistent format:
{
"error": "Not found",
"message": "Note with id 'abc123' does not exist"
}
Common HTTP status codes:
| Status | Meaning |
|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad request (invalid parameters) |
| 401 | Unauthorized (missing or invalid auth) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Resource not found |
| 500 | Internal server error |