# Write records safely

Create and update records with idempotency and optimistic concurrency.

Test writes in a disposable workspace. Discover property metadata first, then create a record with `POST /v2/prism/{teamId}/{objectType}`.

```bash
export IDEMPOTENCY_KEY="$(uuidgen)"
curl --request POST \
  "https://developers.micro.so/v2/prism/${MICRO_TEAM_ID}/contact" \
  --header "content-type: application/json" \
  --header "x-api-key: ${MICRO_API_KEY}" \
  --header "idempotency-key: ${IDEMPOTENCY_KEY}" \
  --data '{"default":{"full_name":"Sarah Chen","email":"sarah@example.com"}}'
```

Generate one key per logical operation. After a timeout or retryable error, resend the same method, path, body, and key. Completed non-5xx responses are retained for 24 hours and replayed with `idempotent-replay: true`. A changed request with the same unexpired key returns `409 idempotency_key_mismatch`; an expired key returns `409 idempotency_key_stale`.

For updates, capture the record's `etag` response header and send it as `If-Match`. A concurrent change then produces `412 precondition_failed` instead of being overwritten.

Never automatically retry a destructive operation without checking its documented semantics and requiring appropriate user confirmation.

## Update without overwriting a newer value

Read the record and save its ETag, then patch with `If-Match`:

```bash
curl --silent --dump-header headers.txt \
  "https://developers.micro.so/v2/prism/${MICRO_TEAM_ID}/contact/${CONTACT_ID}" \
  --header "x-api-key: ${MICRO_API_KEY}"

# Copy the etag header value from headers.txt.
export RECORD_ETAG='"replace-with-etag"'

curl --request PATCH \
  "https://developers.micro.so/v2/prism/${MICRO_TEAM_ID}/contact/${CONTACT_ID}" \
  --header "content-type: application/json" \
  --header "x-api-key: ${MICRO_API_KEY}" \
  --header "if-match: ${RECORD_ETAG}" \
  --header "idempotency-key: $(uuidgen)" \
  --data '{"default":{"full_name":"Sarah Chen-Goldstein"}}'
```

A successful create returns `201` and the new record. A successful patch returns `200`, the updated record, and a new ETag. On `412`, re-read the record and reconcile the user's intended change; do not blindly overwrite it.

## Handle partial success

Bulk update and bulk delete can return HTTP `200` while individual items fail. Inspect every entry in `results` and the response `summary`. Retry only failed items whose error is transient, using a new bulk request and an idempotency key appropriate to that new logical operation. Do not replay successful items as part of a changed request under the old key.

```json
{
  "results": [
    {"id": "11111111-1111-4111-8111-111111111111", "status": "ok", "record": {}},
    {"id": "22222222-2222-4222-8222-222222222222", "status": "error", "error": {"code": "invalid_request", "message": "Invalid property value"}}
  ],
  "summary": {"total": 2, "succeeded": 1, "failed": 1}
}
```

`id` can be `null` when an input item cannot be parsed. Treat `summary.failed > 0` or any `results[].status === "error"` as a partially failed job even though the HTTP request succeeded.
