VoiceMerge

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

  1. 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.
  2. 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.
  3. Send it on every request: Authorization: Bearer vm_pat_….
ScopeLets the tool
tasks:readList projects and tasks, read a task with its thread of notes
notes:writeAttach notes to a task (note, proposal, result)
tasks:statusMove a task open → proposed, proposed → open, approved → done
tasks:writeCreate 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

QueryMeaning
projectA project's id or its name (case-insensitive). Unknown: 404.
statusopen, proposed, approved or done
kindtask, bug or feature
tagOne tag, with or without #
limit1–500, default 100
beforeThe 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:

StatuscodeWhen
401unauthorizedNo token, or it is unknown, expired or revoked
403scopeThe token lacks the scope the endpoint needs
403projectA one-project token asked for another project
404not_found, projectNo such task or project (or the token can't see it)
409transitionA status change a token may not make
422the field's nameA field is missing or out of range
429rate_limitedToo 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."