# Idempotency and ETags

> Retry creates safely with Idempotency-Key; read and write conditionally with ETag, If-None-Match and If-Match; PATCH over PUT.

**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.

**curl**


```bash
# read, keep the ETag
etag=$(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 since
curl -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"}'
```

**Python**


```python
task = tm.tasks.get("T123456")
tm.tasks.update("T123456", {"status": "completed"}, if_match=task.etag)   # 412 if it changed
```

**tm**


```bash
tm tasks update T123456 --status completed   # reads the ETag first, sends If-Match
```

Two identical requests, one Idempotency-Key, one task

## 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-Key` was first used for a different request (method, path or body).
-   Same key, first still running — The first request with this `Idempotency-Key` has not finished; retry shortly.

The operations that honour it:

-   [`projects.create`](https://developers.tasksmate.indrasol.com/reference/operations/projectscreate/) POST /v1/projects
-   [`tasks.create`](https://developers.tasksmate.indrasol.com/reference/operations/taskscreate/) POST /v1/tasks
-   [`task-comments.create`](https://developers.tasksmate.indrasol.com/reference/operations/task-commentscreate/) POST /v1/task-comments
-   [`task-comments.reply`](https://developers.tasksmate.indrasol.com/reference/operations/task-commentsreply/) POST /v1/task-comments/reply
-   [`goals.create`](https://developers.tasksmate.indrasol.com/reference/operations/goalscreate/) POST /v1/projects/{project\_id}/goals

The SDK and `tm` send a key on every create and reuse it for their retries.

## ETags

Single reads return an `ETag`.

-   `If-None-Match` on a read: An `ETag` you hold; unchanged → 304 with no body.
-   `If-Match` on a write: The `ETag` you read; a stale one is a 412 and nothing is written.

## PATCH, not PUT

`PATCH` changes only the fields you send; `null` clears one. Each resource’s `PUT` twin is **deprecated** — prefer `PATCH`:

-   [`projects.replace`](https://developers.tasksmate.indrasol.com/reference/operations/projectsreplace/) PUT /v1/projects/{project\_id}
-   [`tasks.replace`](https://developers.tasksmate.indrasol.com/reference/operations/tasksreplace/) PUT /v1/tasks/{task\_id}
-   [`organization-members.replace`](https://developers.tasksmate.indrasol.com/reference/operations/organization-membersreplace/) PUT /v1/organization-members/{user\_id}/{org\_id}
-   [`designations.replace`](https://developers.tasksmate.indrasol.com/reference/operations/designationsreplace/) PUT /v1/designations/{designation\_id}
-   [`project-members.replace`](https://developers.tasksmate.indrasol.com/reference/operations/project-membersreplace/) PUT /v1/project-members/{user\_id}/{project\_id}
-   [`teams.replace`](https://developers.tasksmate.indrasol.com/reference/operations/teamsreplace/) PUT /v1/teams/{team\_id}
-   [`teams.replace_member`](https://developers.tasksmate.indrasol.com/reference/operations/teamsreplace_member/) PUT /v1/teams/{team\_id}/members/{user\_id}
-   [`goals.replace`](https://developers.tasksmate.indrasol.com/reference/operations/goalsreplace/) PUT /v1/projects/{project\_id}/goals/{goal\_id}

---
Source: https://developers.tasksmate.indrasol.com/guides/idempotency-and-etags/
