Updates, deletes & concurrency
Organization entities (sectors, cost centers, job roles, groups, employees, products) support create, update, and delete. Writes use optimistic concurrency so two integrations editing the same record can never silently clobber each other.
The version token — the ETag header
Every read and write response carries the record's current version in a strong
ETag response header (e.g. ETag: "4"). To update or delete a record, send
that value back in an If-Match request header. The server applies the change
only if the version still matches; otherwise it returns 409 and changes nothing.
The version is exposed only through the ETag header — it is never included in
the response body.
# 1. Read the record; capture its ETag (use -i to see response headers)
curl -i "https://{host}/v1/sectors/sec_123" -H "Authorization: Bearer sk_…"
# → HTTP/2 200
# → ETag: "4"
# → { "data": { "id": "sec_123", "name": "Welding", … } }
# 2. Update, echoing the ETag back as If-Match
curl -X PUT "https://{host}/v1/sectors/sec_123" \
-H "Authorization: Bearer sk_…" \
-H 'If-Match: "4"' \
-H "Content-Type: application/json" \
-d '{ "name": "Welding & Cutting" }'
# → HTTP/2 200
# → ETag: "5" (the new version after your change)
If someone else updated sec_123 between your read and your write, its version is
now 5, your If-Match: "4" is stale, and you get a 409. Re-read to get the
fresh ETag, reapply your change on top of the fresh record, and retry.
You may instead send the version in the request body as "_version": 4. The
If-Match header is preferred (it's the standard HTTP mechanism and keeps the
body purely your data), but both are accepted. If the version is missing on an
update or delete, the request is rejected with 400.
Partial updates — no blanking
An update changes only the fields you send. Fields you omit are left exactly as they were — there is no "replace the whole record" semantics, so you cannot accidentally blank a field by leaving it out.
Delete
DELETE removes the record: it stops appearing in lists and GET-by-id then
returns 404. Delete also requires the current version via If-Match, and returns
200 with the deleted record in the body (not 204):
curl -X DELETE "https://{host}/v1/sectors/sec_123" \
-H "Authorization: Bearer sk_…" \
-H 'If-Match: "5"'
# → HTTP/2 200
# → { "data": { "id": "sec_123", "name": "Welding & Cutting", … } }
Delete is blocked while the entity is still referenced
You cannot delete an entity that other records still point at — that would orphan
those references. If, say, an employee still has a sectorId of the sector you're
deleting (or a group still lists a product in its rules), the delete is rejected
with 409 (type: urn:smartepi:error:resource_in_use) and nothing changes. Re-point or remove
the dependents first, then delete. Deleting a product cascades to its own sizes
and certificates, which is allowed.
Foreign keys
Any foreign key you set on a create or update must exist in your organization, or
the write is rejected with 422 (see Errors). This keeps writes from
ever referencing data that isn't yours.