All topics

REST API reference

The headless API lets scripts and AI agents read and manage a workspace's tasks and notes. Authentication uses API tokens.

Base URL & authentication

The base URL is your deployment's *.convex.site address — the Workspace → API tokens page shows it in a ready-made snippet. Every request sends the token as a bearer:

curl -H "Authorization: Bearer yk_..." \
  "https://<deployment>.convex.site/api/app/tasks"

Errors & rate limits

Errors are JSON with a single error field:

Status Meaning
400 Invalid input — the message says what's wrong
401 Missing, invalid, expired, or revoked token
403 Token lacks the required scope
404 Resource not found (or not in this token's workspace)
429 Rate limit exceeded — retry after Retry-After seconds

Each token gets 120 requests per minute. Error messages are written to be readable by LLMs, so agents can self-correct.

GET /api/app/tasks

Scope: tasks:read. Paginated list of the workspace's tasks.

Query parameters:

  • projectId — only tasks in this project.
  • includeDonetrue to include tasks in terminal columns (default: excluded).
  • limit — page size, 1–200 (default 50).
  • cursor — cursor from the previous page.
curl -H "Authorization: Bearer $YOKE_TOKEN" \
  "$BASE/api/app/tasks?limit=50&includeDone=false"
{
	"tasks": [
		{
			"id": "jd7c...",
			"title": "Send the invoice to Acme",
			"description": null,
			"priority": "high",
			"priorityValue": 3,
			"status": { "name": "To do", "isTerminal": false },
			"project": { "id": "jx2a...", "name": "Inbox" },
			"assignee": null,
			"labels": [{ "id": "jl9k...", "name": "urgent", "color": "red" }],
			"dueDate": 1752192000000,
			"dueDateString": "2026-07-11",
			"createdAt": 1751884800000,
			"source": "manual"
		}
	],
	"cursor": "eyJ...",
	"isDone": false
}

Pagination notes: done-filtering happens after pagination, so a page can be smaller than limit — keep following cursor until isDone is true. Priority sorting applies within a page.

GET /api/app/tasks/:id

Scope: tasks:read. One task with its comments and attachments. Returns { "task": ... }, or 404 if the task isn't in the token's workspace.

POST /api/app/tasks

Scope: tasks:write. Creates a task; responds 201 with the created task.

Body fields:

  • title (required) — string.
  • description — string.
  • priority14 or "low" / "medium" / "high" / "critical" (4/critical shows as "urgent" in the app). Default: low.
  • projectId — target project (default: Inbox). Get ids from /api/app/meta.
  • dueDate — epoch milliseconds.
  • labelIds — array of label ids.
curl -X POST -H "Authorization: Bearer $YOKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Review the PR", "priority": "high", "dueDate": 1752192000000}' \
  "$BASE/api/app/tasks"

PATCH /api/app/tasks/:id

Scope: tasks:write. Partial update — only the fields present in the body change; sending null clears a clearable field.

  • title — string (non-empty).
  • description — string, or null to clear.
  • priority — number or string, as in create.
  • dueDate — epoch ms, or null to clear.
  • projectId — project id, or null to clear.
  • labelIdsreplaces the whole set; include current labels to keep them.
  • status — a column name, matched case-insensitively ("in progress"). Unknown names return 400 listing the available columns. Moving to a terminal column marks the task completed.
curl -X PATCH -H "Authorization: Bearer $YOKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "In progress", "priority": "critical"}' \
  "$BASE/api/app/tasks/jd7c..."

Returns the updated task as { "task": ... }.

POST /api/app/tasks/:id/comments

Scope: tasks:write. Adds a comment authored by the token's owner.

curl -X POST -H "Authorization: Bearer $YOKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"body": "Deployed to staging, please verify."}' \
  "$BASE/api/app/tasks/jd7c.../comments"

Responds 201.

GET /api/app/notes

Scope: notes:read. Metadata of the workspace's notes (no content), newest edits first. Archived notes are excluded. charCount hints how big a note's content is before fetching it.

{
	"notes": [
		{
			"id": "jn4m...",
			"title": "Meeting notes 2026-08-25",
			"folderId": null,
			"projectId": "jx2a...",
			"charCount": 1840,
			"createdAt": 1751884800000,
			"updatedAt": 1752192000000
		}
	]
}

With ?q=<text> the endpoint switches to full-text search over titles and content and returns up to 20 hits as { "id", "title", "snippet", "updatedAt" }.

GET /api/app/notes/:id

Scope: notes:read. One note with its full content as markdown. Returns { "note": ... } with id, title, contentMarkdown, folderId, projectId, archived, createdAt, updatedAt — or 404 if the note isn't in the token's workspace.

POST /api/app/notes

Scope: notes:write. Creates a note; responds 201 with { "id": ... }.

Body fields:

  • title (required) — string.
  • contentMarkdown — the note body as markdown (headings, lists, bold/italic, links, code blocks).
  • folderId — target folder.
  • projectId — link the note to a project.
curl -X POST -H "Authorization: Bearer $YOKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Release checklist", "contentMarkdown": "## Steps\n\n- [ ] tag"}' \
  "$BASE/api/app/notes"

PATCH /api/app/notes/:id

Scope: notes:write. Updates the title and/or content. When contentMarkdown is present, mode is required:

  • "append" — adds the markdown to the end, keeping existing content (including images, which markdown can't carry).
  • "replace" — overwrites the whole body. Fetch the note first if you need the old content.

Sending only title renames the note. Every content edit is saved as a revision. There is no delete — archiving and deleting notes stays in the app.

curl -X PATCH -H "Authorization: Bearer $YOKE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"contentMarkdown": "## Update\n\nShipped v1.2", "mode": "append"}' \
  "$BASE/api/app/notes/jn4m..."

Returns the updated note as { "note": ... }.

GET /api/app/meta

Scope: tasks:read. The workspace's projects, labels, and statuses — fetch this first to map names to ids.

{
	"projects": [{ "id": "jx2a...", "name": "Inbox", "color": "gray" }],
	"labels": [{ "id": "jl9k...", "name": "urgent", "color": "red" }],
	"statuses": [
		{ "id": "js3f...", "name": "To do", "color": "gray", "isTerminal": false },
		{ "id": "js8h...", "name": "Done", "color": "green", "isTerminal": true }
	]
}