---
title: "Concurrency with updated_at and If-Match"
description: "A replace without If-Match is refused, and a stale write gets the current document back."
url: https://docs.spinrun.ai/build/concurrency
markdown_url: https://docs.spinrun.ai/build/concurrency.md
---

# Concurrency with updated_at and If-Match

Every document the server returns carries `updated_at`. It is the version stamp, and it travels twice when you replace a document:

* inside the document, unchanged from the one you read — a body whose `updated_at` differs from the header is rejected with `400`;
* as the `If-Match` header, quoted: `If-Match: "2026-09-01T10:00:00.000Z"`. A replace without it is refused with `428`.

If the document changed since you read it, the reply is `409` with three fields: `error`, `current` — the document as it is now, including its new `updated_at` — and `applied`, the list of stages that had already been written if the conflict surfaced part way through (normally empty). To recover, merge your change into `current`, keep its `updated_at`, and put again. Never retry with the stale document: the conflict exists because someone else's edit is in it.

A create is a `PUT` on the collection (`/api/build/agents`, `/api/build/workflows`) with no `updated_at`; it answers `201`. A replace is a `PUT` on the item, by id or slug.



### CLI

```bash
spinrun agent get triage-bot > agent.json
spinrun agent put --file agent.json
```The CLI sends the document's `updated_at` as `If-Match` for you.

### REST

```bash
STAMP=$(jq -r .updated_at agent.json)
curl -X PUT https://spinrun.ai/api/build/agents/triage-bot \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: \"$STAMP\"" \
  --data-binary @edited.json
```



## Related [#related]

* [Publishing](https://docs.spinrun.ai/build/publishing.md)
* [Edit with curl](https://docs.spinrun.ai/build/edit-with-curl.md)
