0.x — pre-release, no compatibility promise yet.What this means
Idempotency and ETags
What this is: two headers that make retries and concurrent edits safe. When you need it: any create you might retry; any update someone else might race.
# read, keep the ETagetag=$(curl -sI "$TASKSMATE_API_URL/v1/tasks/T123456" -H "Authorization: Bearer $TASKSMATE_TOKEN" \ | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
# write only if nobody changed it sincecurl -X PATCH "$TASKSMATE_API_URL/v1/tasks/T123456" \ -H "Authorization: Bearer $TASKSMATE_TOKEN" -H "If-Match: $etag" \ -H "Content-Type: application/json" -d '{"status": "completed"}'task = tm.tasks.get("T123456")tm.tasks.update("T123456", {"status": "completed"}, if_match=task.etag) # 412 if it changedtm tasks update T123456 --status completed # reads the ETag first, sends If-MatchIdempotent creates
Section titled “Idempotent creates”Idempotency-Key: Replay the first response for 24 h when the same request is retried with this key. A replayed answer carries
Idempotent-Replayed: true. Keys live 24 h; use a fresh UUID per logical create.
- Same key, different request — This
Idempotency-Keywas first used for a different request (method, path or body). - Same key, first still running — The first request with this
Idempotency-Keyhas not finished; retry shortly.
The operations that honour it:
projects.createPOST /v1/projectstasks.createPOST /v1/taskstask-comments.createPOST /v1/task-commentstask-comments.replyPOST /v1/task-comments/replygoals.createPOST /v1/projects/{project_id}/goals
The SDK and tm send a key on every create and reuse it for their retries.
Single reads return an ETag.
If-None-Matchon a read: AnETagyou hold; unchanged → 304 with no body.If-Matchon a write: TheETagyou read; a stale one is a 412 and nothing is written.
PATCH, not PUT
Section titled “PATCH, not PUT”PATCH changes only the fields you send; null clears one. Each resource’s PUT twin is deprecated — prefer
PATCH:
projects.replacePUT /v1/projects/{project_id}tasks.replacePUT /v1/tasks/{task_id}organization-members.replacePUT /v1/organization-members/{user_id}/{org_id}designations.replacePUT /v1/designations/{designation_id}project-members.replacePUT /v1/project-members/{user_id}/{project_id}teams.replacePUT /v1/teams/{team_id}teams.replace_memberPUT /v1/teams/{team_id}/members/{user_id}goals.replacePUT /v1/projects/{project_id}/goals/{goal_id}