VoiceMerge API — projects, tasks and notes for your own tools
An API token lets a tool of yours (an AI agent, a script, a CI job) work on the tasks you keep in VoiceMerge: read them by project, tag, kind or status, attach notes, propose a plan, and mark what you approved as done. The person always has the last word: a token can never approve.
Base URL: https://api.voicemerge.app. Tokens are made on your account page.
Before you start
- Turn sync on. Tasks live on the device until you switch on Tasks → More → Sync and destinations → Sync tasks with my VoiceMerge account. Only synced tasks exist on the server, so only they are visible to a token. The words you spoke are never synced.
- Create a token at https://voicemerge.app/account/, under API tokens. Name it after the tool, choose what it may do, and optionally limit it to one project. The token (
vm_pat_…) is shown once. - Send it on every request:
Authorization: Bearer vm_pat_….
| Scope | Lets the tool |
|---|---|
tasks:read | List projects and tasks, read a task with its thread of notes |
notes:write | Attach notes to a task (note, proposal, result) |
tasks:status | Move a task open → proposed, proposed → open, approved → done |
tasks:write | Create tasks (as open or proposed) |
A token limited to one project sees and touches nothing outside it. Revoking a token (same page) stops it at once. Limits: 240 requests a minute per account; 20 live tokens per account.
The loop
you (phone or Mac) your tool (token)
────────────────── ─────────────────
say or type a bug ─────▶ GET /v1/work/tasks?project=Website&status=open&kind=bug
POST /v1/work/tasks/{id}/notes {"kind":"proposal","body":"…plan…"}
POST /v1/work/tasks/{id}/status {"status":"proposed"}
read the plan, Approve ─────▶ GET /v1/work/tasks?status=approved
…does the work…
POST /v1/work/tasks/{id}/notes {"kind":"result","body":"…what was done…"}
POST /v1/work/tasks/{id}/status {"status":"done"}
see it done, with notes ◀────
Everything a tool writes reaches your devices with their next sync, with the token's name as the author.
Records
A task:
{
"id": "4d7ad743-e0b2-47fb-a525-06e77591d6a2",
"project_id": "eea36a15-d6b3-49d2-a2b3-4e8680a353f5",
"project": "Website",
"kind": "bug", // task | bug | feature
"status": "open", // open | proposed | approved | done
"title": "Login fails on iOS",
"notes": "after the update", // the task's own text
"tags": ["auth"],
"people": ["Maria"],
"due": "2026-10-03T00:00:00.000Z", "due_has_time": false,
"priority": 3, // 0 none, 1 low, 2 medium, 3 high
"created_at": "2026-09-30T05:07:30.321Z",
"updated_at": "2026-09-30T05:07:30.321Z",
"completed_at": null,
"rev": 2,
"thread": [ /* notes attached to it, oldest first; only on GET /v1/work/tasks/{id} */ ]
}
A note in a task's thread:
{ "id": "…", "task_id": "…", "kind": "proposal", "title": "Plan", "body": "Refresh the token before retrying.",
"author": "Fixer", "created_at": "…", "updated_at": "…", "rev": 5 }
author is the token's name; it is absent on notes the person wrote. kind is note, proposal (a plan waiting for approval) or result (what was done). Times are UTC, ISO 8601, with milliseconds.
Endpoints
GET /v1/work/projects — tasks:read
{ "projects": [ { "id": "…", "name": "Website", "archived": false, "updated_at": "…" } ] }
GET /v1/work/tasks — tasks:read
| Query | Meaning |
|---|---|
project | A project's id or its name (case-insensitive). Unknown: 404. |
status | open, proposed, approved or done |
kind | task, bug or feature |
tag | One tag, with or without # |
limit | 1–500, default 100 |
before | The next value from the previous page |
Most recently changed first. { "tasks": [ … ], "next": 17 }; next is absent on the last page.
GET /v1/work/tasks/{id} — tasks:read
The task with its thread.
POST /v1/work/tasks/{id}/notes — notes:write
{ "kind": "proposal", "title": "Plan", "body": "…up to 200,000 characters, Markdown welcome…" }
kind defaults to note; title is optional. Returns the note.
POST /v1/work/tasks/{id}/status — tasks:status
{ "status": "proposed" }
Allowed for a token: open → proposed (I have a plan), proposed → open (I take it back), approved → done (I did what you approved). Anything else answers 409 with code transition. Setting the status a task already has is accepted and changes nothing. Returns the task.
POST /v1/work/tasks — tasks:write
{ "title": "Footer links are broken", "kind": "bug", "project": "Website", "notes": "", "tags": ["web"],
"people": [], "priority": 2, "status": "open" }
Only title is required. project is a project's name (made if it doesn't exist); project_id names one by id. A token creates tasks as open or proposed. A token limited to one project files everything there. Returns the task.
Errors
Problems are JSON (RFC 7807) with a code and a detail written for a person:
| Status | code | When |
|---|---|---|
| 401 | unauthorized | No token, or it is unknown, expired or revoked |
| 403 | scope | The token lacks the scope the endpoint needs |
| 403 | project | A one-project token asked for another project |
| 404 | not_found, project | No such task or project (or the token can't see it) |
| 409 | transition | A status change a token may not make |
| 422 | the field's name | A field is missing or out of range |
| 429 | rate_limited | Too many requests; wait a minute |
Example: an agent's polling pass
API=https://api.voicemerge.app
AUTH="Authorization: Bearer $VOICEMERGE_TOKEN"
# 1. New bugs to look at
curl -s -H "$AUTH" "$API/v1/work/tasks?project=Website&status=open&kind=bug"
# 2. Propose a fix for one
curl -s -H "$AUTH" -H 'Content-Type: application/json' -X POST "$API/v1/work/tasks/$ID/notes" \
-d '{"kind":"proposal","title":"Plan","body":"Refresh the token before retrying; add a test."}'
curl -s -H "$AUTH" -H 'Content-Type: application/json' -X POST "$API/v1/work/tasks/$ID/status" -d '{"status":"proposed"}'
# 3. Later: what has been approved?
curl -s -H "$AUTH" "$API/v1/work/tasks?status=approved"
# 4. After doing the work
curl -s -H "$AUTH" -H 'Content-Type: application/json' -X POST "$API/v1/work/tasks/$ID/notes" \
-d '{"kind":"result","body":"Fixed in 1a2b3c and deployed."}'
curl -s -H "$AUTH" -H 'Content-Type: application/json' -X POST "$API/v1/work/tasks/$ID/status" -d '{"status":"done"}'
For an AI agent, the instruction that goes with the token can be this short: "List approved tasks in project X. For each, read the thread, carry out the approved proposal, attach a result note saying what you did, and mark it done. For open bugs and feature requests, attach a proposal and mark them proposed. Never do work that is not approved."