tasuki API guide
For developers and agent builders. This page covers the public REST API and MCP surface.
1. Overview and base URLs
tasuki is a task system for people and agents. The same account data is available over REST and MCP.
| Surface | Base |
|---|---|
| Web app | https://app.usetasuki.com |
| REST and MCP | https://api.usetasuki.com (alias of the app host for token traffic) |
REST lives under /api/v1. OpenAPI is at GET /openapi.json and is the source of truth for routes and fields. Every REST response carries X-Tasuki-API-Version: 1.
Discover features with:
GET /api/v1/capabilities
No sign-in is needed for the static part. The body includes API version, server build, feature flags, limits, rate-limit policy, supported MCP protocol versions, and deprecations. Prefer this over guessing from a version string.
2. Authentication
Call REST with a bearer credential:
Authorization: Bearer YOUR_TOKEN
A missing, invalid, expired or revoked credential returns 401 with WWW-Authenticate set.
Personal access tokens and agents
Agents are created with POST /agents. The create response holds a one-time token that is never stored again. That token is scoped to the account (and optionally to projects).
A personal access token can also carry the webhooks scope. That scope is not part of any trust level. Tick it under Advanced when you create the token.
Scopes and trust presets
Scope is checked before existence. A grant that lacks a scope returns 403 (<agent> lacks '<scope>' scope). Another account’s data, or a task outside an agent’s projects, returns 404, never 403.
Trust levels for agents:
| Level | Can |
|---|---|
| suggest | Propose only (status: proposed). New field values are allowed on a proposal. |
| trusted | Add and edit tasks. Create and update projects. |
| full | Also complete and delete tasks, and delete projects. |
Useful scopes called out in the contract: read, complete, webhooks. Completing needs complete. Humans (and the native iOS app acting as the person) can call human-only routes such as accept, reject, settings, import and export.
A native app token is refused on /mcp.
3. Conventions
Versioning
Within /api/v1 and the MCP tool set, v1 is additive only. tasuki never removes or renames a route, field, tool, argument, scope or response key; never changes a field’s type or meaning; never tightens validation so yesterday’s request fails today; never changes an existing success or error status.
tasuki may add routes, tools, optional fields, response keys and open enum values. Closed enums (status, next_actor) do not grow silently. Clients must ignore unknown response keys. Feature-detect with /capabilities. A breaking change would be /api/v2 beside v1.
Nothing is deprecated today (capabilities.deprecations is []). When something is, it is listed there with replacement and sunset, and responses carry Deprecation and Sunset headers. Sunset is at least 90 days after the changelog entry.
Errors
Every error is JSON:
{"error": "<human readable message>"}
Extra keys are additive per route (code, current, open_subtasks, evidence_required, reason, retry_after_seconds, and so on).
| Status | Meaning |
|---|---|
| 400 | Bad JSON, unknown field, wrong type, validation |
| 401 | Bad or missing credential |
| 403 | Missing scope, human-only action, or project create limit |
| 404 | Missing or not visible to you |
| 409 | Version conflict, open sub-tasks, caps, undo refused, idempotency in flight |
| 413 | Body over 256 KB |
| 422 | Idempotency key reused with a different request |
| 429 | Rate limited |
| 500 | Server bug (no internals in the body) |
Pagination
GET /tasks with no paging args still returns a bare array. With limit (1–500, default 50), offset or cursor the body is:
{"tasks": [], "total": 0, "limit": 50, "offset": 0, "next_cursor": "..."}
Follow next_cursor until it is absent. Cursors are opaque. Batches are at most 50 items per request.
Rate limits
Fixed window per credential: 600 requests per minute (iOS app: 240). A batch counts as one request. Every /api/v1 response includes RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 also sends Retry-After.
Idempotency
REST: header Idempotency-Key: <1–200 printable characters, no spaces> on POST, PATCH, PUT, DELETE.
MCP: argument idempotency_key on write tools (add_task, update_task, complete_task, delete_task, add_tasks, update_tasks, add_comment, undo_change).
Same key and same request within 24 hours returns the original result with idempotent_replay: true (and REST header Idempotent-Replay: true). Same key with a different request is 422 idempotency_key_conflict. An in-flight key is 409 with Retry-After: 1. Only successful results are stored. Keys are per account. POST /agents ignores the key. Dry runs never touch the key table.
POST /tasks / add_task by an agent also de-duplicates an open task with the same normalised title in the same project (and same source if given), returning it with duplicate_of_existing: true (REST 200). Pass allow_duplicate: true to opt out.
Dry run
?dry_run=true (REST) or dry_run: true (MCP) on add, update, complete and delete returns something like:
{
"dry_run": true,
"would_apply": true,
"would_require_review": false,
"diff": {"before": null, "after": {}, "changed": []},
"trust_check": {}
}
Nothing is written. Scope, trust and validation still apply, so a denied action is denied in a dry run too.
Dates are YYYY-MM-DD, times HH:MM (24 h), in the user’s timezone (GET /api/v1/settings, default Australia/Perth). Empty string clears an optional string or date. null or 0 clears numbers. Unknown request fields are still 400.
4. Tasks
Key fields
Core fields: id, title, status (todo | doing | done | proposed), next, due, effort, project, notes, origin, created, updated, version.
Also:
| Field | Notes |
|---|---|
next_action |
Alias of next. Responses carry both. |
next_actor |
human | agent | other |
next_actor_name |
Up to 80 chars |
waiting_on / waiting_until |
Blocker text and follow-up date |
due_date, due_time, due_tz |
Structured due; due is legacy |
start_date |
Hide until |
recurrence |
Object or text on input |
priority |
1–4 (1 highest) |
estimate_minutes |
Integer |
project / project_id |
Name and/or id; must agree if both sent |
labels |
Up to 10, each up to 40 chars |
parent_id |
One level of sub-tasks |
done_when |
Checklist, up to 20 items |
evidence |
{text?, links?[]} on complete |
source |
Provenance {url?, label?} |
Task ids look like t_ plus hex. Treat them as opaque.
List and get
GET /api/v1/tasks?status=todo&project_id=p_...&limit=50
Authorization: Bearer YOUR_TOKEN
Filters include status, project, project_id, label, priority, due helpers (due_before, due_after, due_on, due_today, overdue, has_due), q, next_actor, waiting, parent_id, top_level, available, sort, order, and paging.
POST /api/v1/tasks
Content-Type: application/json
{"title": "Send quote", "project": "Sales", "next_actor": "human", "priority": 2}
Complete
POST /api/v1/tasks/{id}/complete
Content-Type: application/json
{"evidence": {"text": "Quote emailed", "links": ["https://..."]}, "complete_children": true}
If the owner requires agent evidence, an agent completion without evidence is 400 with evidence_required: true. Open sub-tasks are 409 unless complete_children: true. Completing a recurring task creates the next instance and may return recurred_to.
Comments, dependencies, batch
Comments appear on the change feed as comment events with parent: {entity: "task", id}. MCP write tools include add_comment. Sub-tasks use parent_id (one level). Batch writes accept at most 50 items (add_tasks, update_tasks).
Related reads: GET /api/v1/today, /projects, /labels, and human accept/reject on proposed tasks.
5. The change feed and delta sync
One ordered stream of account changes, built from the activity log in the same transaction as each write.
GET /api/v1/changes?format=events&since=<cursor>&limit=100&view=compact
Needs read. Response shape:
{
"events": [],
"next_cursor": "c_412",
"has_more": false,
"reset": false,
"retention_days": 90,
"workspace_id": "w_..."
}
Event sketch:
{
"id": "ev_411",
"cursor": "c_411",
"at": "2026-11-04T01:02:03.456Z",
"type": "task.completed",
"entity": "task",
"entity_id": "t_1a2b3c",
"action": "completed",
"actor": {"kind": "agent", "name": "Hermes", "id": "ag_..."},
"origin": "mcp",
"change_id": "411",
"version": 5,
"project_id": "p_...",
"changed": ["completed_at", "status"],
"after": {"id": "t_1a2b3c", "title": "Send quote", "status": "done", "version": 5}
}
Guarantees: strict cursor order, at-least-once delivery, de-dupe on event id, never another account’s data. Retention window for the feed is 90 days. An old or ahead cursor returns reset: true and a baseline fetch list. On resync: "tasks", re-list tasks. Project-limited agents only see their projects; a task leaving scope arrives as a delete with scope_exit: true.
Suggested sync:
GET .../changes?format=events&since=nowand storenext_cursor.- Fetch baseline: tasks, projects (
?archived=trueorinclude_archived=trueas documented), labels. - Poll with
since=<cursor>; apply byentity/entity_id. - On
reset: true, start again.
Long-poll: add &wait=25 (0–25 seconds). At most 3 waiting requests per account.
The older GET /changes?since=<seq> task-snapshot feed remains for the iOS app.
6. Webhooks
Webhooks deliver the same change events. Create with a human session or a PAT that has the webhooks scope:
POST /api/v1/webhooks
Content-Type: application/json
{"url": "https://example.com/tasuki-hook", "events": ["task.created", "suggestion.created"]}
{"id": "wh_...", "url": "...", "events": [], "created_at": "...", "secret": "***"}
The secret is returned once. Limits: 5 webhooks per user; HTTPS only; public hostnames; no private DNS targets; redirects never followed; 10 s timeout. Allowed event names include task.created, task.updated, task.completed, task.deleted, suggestion.created, suggestion.accepted, suggestion.rejected, comment.created.
Delivery headers: Tasuki-Event, Tasuki-Delivery, Tasuki-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Reject when |now - t| > 300 seconds. De-dupe on Tasuki-Delivery or body id. Retries: 6 attempts over about 55 minutes. 50 consecutive failures auto-disables the hook.
Verify (curl)
t=1793000000; body='{"id":"ev_1"}'
printf '%s.%s' "$t" "$body" | openssl dgst -sha256 -hmac "$WHSEC" -hex
Verify (Node)
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, rawBody, header, now = Date.now() / 1000) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!m || Math.abs(now - Number(m[1])) > 300) return false;
const want = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest();
return timingSafeEqual(want, Buffer.from(m[2], "hex"));
}
Verify (Python)
import hmac, hashlib, re, time
def verify(secret: str, raw_body: bytes, header: str, now=None) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m or abs((now or time.time()) - int(m[1])) > 300:
return False
want = hmac.new(secret.encode(), m[1].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, m[2])
List and delete: GET /api/v1/webhooks, DELETE /api/v1/webhooks/{id}. Deliveries: GET /api/v1/webhooks/{id}/deliveries (last 50, no bodies stored). Test: POST /api/v1/webhooks/{id}/test sends a signed webhook.ping.
7. MCP
Point your MCP client at the tasuki MCP endpoint on api.usetasuki.com with the agent token. Supported protocol versions: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05. Structured tool output (outputSchema / structuredContent) is only sent on 2025-06-18 or newer; text content is always present.
Tools by group
Capabilities and sync
get_capabilitiesget_changesget_today
Tasks
list_tasks,get_taskadd_task,update_task,complete_task,delete_taskadd_tasks,update_tasksadd_comment
Projects and labels
list_projects,create_project,update_projectlist_labels
Review signal
list_rejections
Safety
undo_change(withidempotency_key/dry_runas for other writes)
Server instructions tell agents to read get_today first and list_rejections before proposing. Trust levels match REST (suggest / trusted / full).
8. Undo and safety
- Review queue. Suggest-only agents create
proposedtasks. Humans accept (POST /api/v1/tasks/{id}/accept) or reject (.../rejectwith optionalreason_code). Feed entityagent-suggestioncovers proposed, accepted and rejected. - Dry run validates scope and trust without writing.
- Scopes and project limits bound what a token can see and change. Restricted webhooks narrow with the agent.
- Evidence. Owners can set
require_agent_evidenceso agent completions need evidence. - Undo. Change events carry
change_idforPOST /api/v1/changes/{id}/undo. Activity entries are revertible withPOST /api/v1/activity/{n}/revert(human). Undo of a recurring completion also removes the untouched next instance. A refused undo is 409 with areasonwhen the entity changed since. - Audit. The activity log and change feed are the audit trail. Webhook delivery logs store attempt metadata only, never request or response bodies.
When in doubt, call GET /api/v1/capabilities, prefer dry run on writes, and treat OpenAPI as canonical for field-level detail.